/*-------------------------------------------------------------------------
 *
 * namespace.c
 *	  支持访问和搜索命名空间的代码
 *
 * 这与pg_namespace.c是分开的，后者包含直接操作pg_namespace系统目录的常规例程。该模块
 * 提供与定义“命名空间搜索路径”相关的常规例程，并实现搜索路径控制的搜索。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/catalog/namespace.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/parallel.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/dependency.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/sinvaladt.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/varlena.h"



/*
 * 命名空间搜索路径是一个可能为空的命名空间 OID 列表。
 * 除了显式列表外，隐式搜索的命名空间
 * 也可以包括：
 *
 * 1. 如果在该会话中初始化了 TEMP 表命名空间，
 * 它将首先被隐式搜索。 （只有在遵循一个覆盖搜索路径规范时，这种情况才不会发生，该规范表示不使用临时命名空间，或者临时命名空间包含在显式列表中。）
 *
 * 2. 系统目录命名空间始终被搜索。如果系统
 * 命名空间在显式路径中存在，则将按指定顺序进行搜索；否则，它将首先在 TEMP 表后搜索，
 * *在*显式列表之前。 （可能看起来系统命名空间
 * 应该隐式最后，但这种行为似乎是 SQL99 所要求的。
 * 此外，这提供了一种优先搜索系统命名空间的方法，
 * 而不因此而使其成为默认创建目标命名空间。）
 *
 * 出于安全原因，使用搜索路径进行的搜索在搜索
 * 除关系和类型之外的任何对象类型时将忽略临时命名空间。
 * （我们必须允许类型，因为临时表具有行类型。）
 *
 * 默认创建目标命名空间始终是
 * 显式列表的第一个元素。如果显式列表为空，则没有默认目标。
 *
 * search_path 的文本规范可以包括 "$user" 以引用与
 * 当前用户同名的命名空间（如果存在）。 （如果没有这样的命名空间，则会被忽略。）
 * 此外，它可以包括 "pg_temp"
 * 以引用当前后端的临时命名空间。如果临时命名空间尚未设置，这通常也是可忽略的，但有一个特殊情况：
 * 如果 "pg_temp" 首先出现，则它应该是默认创建
 * 目标。我们会稍微调节这种情况，以便在首次尝试在其中创建某些内容之前，不设置临时命名空间。
 * （这种调整的原因是我们不能在事务外创建临时命名空间，
 * 但 search_path 的初始 GUC 处理发生在事务之外。）
 * 当 "pg_temp" 首先出现在字符串中时，activeTempCreationPending 为 true，
 * 但在 activeCreationNamespace 中没有反映，因为命名空间尚未设置。
 *
 * 在引导模式下，搜索路径设置为 "pg_catalog"，以便
 * 系统命名空间是唯一被搜索或插入的命名空间。
 * initdb 还小心地将 search_path 设置为 "pg_catalog" 以便于其
 * 引导后独立后端运行。否则，默认搜索
 * 路径由 GUC 决定。工厂默认路径包含 PUBLIC
 * 命名空间（如果存在），并在用户的个人命名空间之前
 * （如果存在）。
 *
 * 我们支持一组 “覆盖” 搜索路径设置，用于特定的
 * 后端代码部分。在覆盖堆栈非空时，namespace_search_path 被忽略。
 * activeSearchPath 始终是实际有效的路径；它指向堆栈顶部条目的搜索列表，
 * 或指向从 namespace_search_path 派生的 baseSearchPath 列表。
 *
 * 如果 baseSearchPathValid 为 false，则需要根据 namespace_search_path
 * 重新计算 baseSearchPath（和其他派生变量）。
 * 我们在对 namespace_search_path 赋值或接收
 * pg_namespace 的 syscache 失效事件时将其标记为无效。
 * 重新计算将在下一个非覆盖的查找尝试期间进行。
 * 请注意，覆盖规范从不需要重新计算。
 *
 * 在 namespace_search_path 中提到的任何当前用户 ID
 * 无法读取的命名空间将被简单地排除在 baseSearchPath 之外；
 * 因此，当当前用户 ID 更改时，我们必须愿意重新计算路径。
 * namespaceUser 是为其计算路径的用户 ID。
 *
 * 注意：这些 List 变量指向的所有数据都在 TopMemoryContext 中。
 *
 * activePathGeneration 在 activeSearchPath/activeCreationNamespace/activeTempCreationPending 的有效值发生变化时递增。
 * 这可以用于快速检测自上一次检查搜索路径状态以来是否发生了任何变化。
 */

/* 这些变量定义了实际的活动状态： */

static List *activeSearchPath = NIL;

/* 创建东西的默认位置；如果为 InvalidOid，则没有默认值 */
static Oid	activeCreationNamespace = InvalidOid;

/* 如果为 true，则 activeCreationNamespace 是错误的，它应该是临时命名空间 */
static bool activeTempCreationPending = false;

/* 当前生成计数器；确保这个值永远不为零 */
static uint64 activePathGeneration = 1;

/* 这些变量是从 namespace_search_path 中最后派生的值： */

static List *baseSearchPath = NIL;

static Oid	baseCreationNamespace = InvalidOid;

static bool baseTempCreationPending = false;

static Oid	namespaceUser = InvalidOid;

/* 上述四个值仅在 baseSearchPathValid 为 true 时有效 */
static bool baseSearchPathValid = true;

/* 覆盖请求在一组 OverrideStackEntry 结构中被记住 */

typedef struct
{
	List	   *searchPath;		/* 所需的搜索路径 */
	Oid			creationNamespace;	/* 所需的创建命名空间 */
	int			nestLevel;		/* 子事务嵌套级别 */
} OverrideStackEntry;

static List *overrideStack = NIL;


/*
 * myTempNamespace 是 InvalidOid，直到在特定的后端会话中设置了 TEMP 命名空间（这发生在首次执行 CREATE TEMP TABLE 命令时）。此后，它是临时命名空间的 OID。
 *
 * myTempToastNamespace 是我的临时表的 toast 表的命名空间的 OID。当 myTempNamespace 被设置时，它也会被设置，并在那之前是 InvalidOid。
 *
 * myTempNamespaceSubID 显示我们是否在当前子事务中创建了 TEMP 命名空间。该标志会沿着子事务树向上传播，因此如果所有中间子事务提交，主事务将正确识别该标志。当它是 InvalidSubTransactionId 时，我们要么还没有创建 TEMP 命名空间，要么已经成功提交了其创建，这取决于 myTempNamespace 是否有效。
 */
static Oid	myTempNamespace = InvalidOid;

static Oid	myTempToastNamespace = InvalidOid;

static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId;

/*
 * 这是用户的文本搜索路径规范——它是 GUC 变量 'search_path' 的值。
 */
char	   *namespace_search_path = NULL;


/* 本地函数 */
static void fc_recomputeNamespacePath(void);
static void fc_AccessTempTableNamespace(bool fc_force);
static void fc_InitTempTableNamespace(void);
static void fc_RemoveTempRelations(Oid fc_tempNamespaceId);
static void fc_RemoveTempRelationsCallback(int fc_code, Datum fc_arg);
static void fc_NamespaceCallback(Datum fc_arg, int fc_cacheid, uint32 fc_hashvalue);
static bool fc_MatchNamedCall(HeapTuple fc_proctup, int fc_nargs, List *fc_argnames,
						   bool fc_include_out_arguments, int fc_pronargs,
						   int **fc_argnumbers);


/*
 * RangeVarGetRelidExtended
 *		给定一个描述现有关系的 RangeVar，
 *		选择适当的命名空间并查找关系 OID。
 *
 * 如果未找到架构或关系，如果标志包含 RVR_MISSING_OK，则返回 InvalidOid，否则引发错误。
 *
 * 如果标志包含 RVR_NOWAIT，如果我们必须等待锁，则抛出错误。
 *
 * 如果标志包含 RVR_SKIP_LOCKED，如果我们必须等待锁，则返回 InvalidOid。
 *
 * 标志不能同时包含 RVR_NOWAIT 和 RVR_SKIP_LOCKED。
 *
 * 注意，如果同时指定了 RVR_MISSING_OK 和 RVR_SKIP_LOCKED，则返回值 InvalidOid 可能意味着关系缺失或者无法锁定。
 *
 * 回调允许调用者在获取关系锁之前检查权限或获取额外的锁。
 */
Oid RangeVarGetRelidExtended(const RangeVar *fc_relation, LOCKMODE fc_lockmode,
						 uint32 fc_flags,
						 RangeVarGetRelidCallback fc_callback, void *fc_callback_arg)
{
	uint64		fc_inval_count;
	Oid			fc_relId;
	Oid			fc_oldRelId = InvalidOid;
	bool		fc_retry = false;
	bool		fc_missing_ok = (fc_flags & RVR_MISSING_OK) != 0;

	/* 验证标志没有冲突 */
	Assert(!((fc_flags & RVR_NOWAIT) && (fc_flags & RVR_SKIP_LOCKED)));

	/*
	 * 我们检查目录名称，然后忽略它。
	 */
	if (fc_relation->catalogname)
	{
		if (strcmp(fc_relation->catalogname, get_database_name(MyDatabaseId)) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
							fc_relation->catalogname, fc_relation->schemaname,
							fc_relation->relname)));
	}

	/*
	 * DDL 操作可能会改变名称查找的结果。由于所有这些操作都会生成无效消息，因此我们跟踪在执行操作时是否出现任何此类消息，并重试直到（1）没有更多无效消息显示或（2）答案没有改变。
	 *
	 * 但如果 lockmode = NoLock，则假设调用者对答案在他们面前改变没有问题，或者他们已经持有某些适当的锁，因此返回我们获得的第一个答案，而不检查无效消息。此外，如果请求的锁已经被持有，LockRelationOid 将不接受无效消息，因此我们可能会未能注意到变化。我们可以通过在开始此循环之前调用 AcceptInvalidationMessages() 来防止这种情况，但那会增加显著的开销，因此现在我们不这样做。
	 */
	for (;;)
	{
		/*
		 * 记住这个值，以便在查找关系名称并锁定其 OID 后，我们可以检查是否处理了任何可能需要重新处理的无效消息。
		 */
		fc_inval_count = SharedInvalidMessageCounter;

		/*
		 * 可能指定了一些非默认的 relpersistence 值。解析器在简单 DML 中从不生成这样的 RangeVar，但在诸如 "CREATE TEMP TABLE foo (f1 int PRIMARY KEY)" 这样的上下文中可能会发生。这类命令将生成添加的 CREATE INDEX 操作，必须小心查找临时表，即使 pg_temp 在搜索路径中不是第一个。
		 */
		if (fc_relation->relpersistence == RELPERSISTENCE_TEMP)
		{
			if (!OidIsValid(myTempNamespace))
				fc_relId = InvalidOid; /* 这可能不应该发生？ */
			else
			{
				if (fc_relation->schemaname)
				{
					Oid			fc_namespaceId;

					fc_namespaceId = LookupExplicitNamespace(fc_relation->schemaname, fc_missing_ok);

					/*
					 * 对于 missing_ok，允许一个不存在的架构名称返回 InvalidOid。
					 */
					if (fc_namespaceId != myTempNamespace)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
								 errmsg("temporary tables cannot specify a schema name")));
				}

				fc_relId = get_relname_relid(fc_relation->relname, myTempNamespace);
			}
		}
		else if (fc_relation->schemaname)
		{
			Oid			fc_namespaceId;

			/* 使用给定的确切架构 */
			fc_namespaceId = LookupExplicitNamespace(fc_relation->schemaname, fc_missing_ok);
			if (fc_missing_ok && !OidIsValid(fc_namespaceId))
				fc_relId = InvalidOid;
			else
				fc_relId = get_relname_relid(fc_relation->relname, fc_namespaceId);
		}
		else
		{
			/* 搜索命名空间路径 */
			fc_relId = RelnameGetRelid(fc_relation->relname);
		}

		/*
		 * 如果存在调用者提供的回调，调用之。
		 *
		 * 此回调是检查权限的好位置：我们还没有
		 * 锁定表（在锁定任何东西之前检查权限实际上是最好的！），但我们已经走得足够远，可以知道我们
		 * 认为应该锁定的 OID。当然， 并发的 DDL 可能会在我们等待锁定时
		 * 改变情况，但在这种情况下，回调将再次被调用以处理新的 OID。
		 */
		if (fc_callback)
			fc_callback(fc_relation, fc_relId, fc_oldRelId, fc_callback_arg);

		/*
		 * 如果没有请求锁定，我们假设调用者知道自己在做什么。
		 * 他们应该在处理同一语句的早期阶段已经获得了此关系的重量级锁定，
		 * 因此，在这里调用 AcceptInvalidationMessages() 并不合适，因为
		 * 这可能会让他们失去支撑。
		 */
		if (fc_lockmode == NoLock)
			break;

		/*
		 * 如果在重试时，我们得到与上次相同的 OID，
		 * 那么我们处理的无效消息并没有改变最终的答案。
		 * 所以我们完成了。
		 *
		 * 如果我们得到了一个不同的 OID，我们锁定的是以前
		 * 有这个名称的关系，而不是现在有这个名称的关系。所以释放
		 * 锁定。
		 */
		if (fc_retry)
		{
			if (fc_relId == fc_oldRelId)
				break;
			if (OidIsValid(fc_oldRelId))
				UnlockRelationOid(fc_oldRelId, fc_lockmode);
		}

		/*
		 * 锁定关系。这也将接受任何待处理的无效
		 * 消息。如果我们得到了 InvalidOid，表示未找到，那么
		 * 就没有什么可锁定的，但我们仍然接受无效消息，
		 * 以刷新可能仍然存在的负 catcache 条目。
		 */
		if (!OidIsValid(fc_relId))
			AcceptInvalidationMessages();
		else if (!(fc_flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
			LockRelationOid(fc_relId, fc_lockmode);
		else if (!ConditionalLockRelationOid(fc_relId, fc_lockmode))
		{
			int			fc_elevel = (fc_flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;

			if (fc_relation->schemaname)
				ereport(fc_elevel,
						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
						 errmsg("could not obtain lock on relation \"%s.%s\"",
								fc_relation->schemaname, fc_relation->relname)));
			else
				ereport(fc_elevel,
						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
						 errmsg("could not obtain lock on relation \"%s\"",
								fc_relation->relname)));

			return InvalidOid;
		}

		/*
		 * 如果没有处理无效消息，我们完成了！
		 */
		if (fc_inval_count == SharedInvalidMessageCounter)
			break;

		/*
		 * 可能发生了一些变化。让我们重复名称查找，以确保
		 * 这个名称仍然引用之前相同的关系。
		 */
		fc_retry = true;
		fc_oldRelId = fc_relId;
	}

	if (!OidIsValid(fc_relId))
	{
		int			fc_elevel = fc_missing_ok ? DEBUG1 : ERROR;

		if (fc_relation->schemaname)
			ereport(fc_elevel,
					(errcode(ERRCODE_UNDEFINED_TABLE),
					 errmsg("relation \"%s.%s\" does not exist",
							fc_relation->schemaname, fc_relation->relname)));
		else
			ereport(fc_elevel,
					(errcode(ERRCODE_UNDEFINED_TABLE),
					 errmsg("relation \"%s\" does not exist",
							fc_relation->relname)));
	}
	return fc_relId;
}

/*
 * RangeVarGetCreationNamespace
 *		给定一个描述要创建的关系的 RangeVar，
 *		选择要在哪个命名空间中创建它。
 *
 * 注意：调用此方法可能会导致 CommandCounterIncrement 操作。
 * 这将在特定后端运行中的首次请求临时表时发生；
 * 我们需要创建或清理临时架构。
 */
Oid RangeVarGetCreationNamespace(const RangeVar *fc_newRelation)
{
	Oid			fc_namespaceId;

	/*
	 * 我们检查目录名称，然后忽略它。
	 */
	if (fc_newRelation->catalogname)
	{
		if (strcmp(fc_newRelation->catalogname, get_database_name(MyDatabaseId)) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
							fc_newRelation->catalogname, fc_newRelation->schemaname,
							fc_newRelation->relname)));
	}

	if (fc_newRelation->schemaname)
	{
		/* 检查 pg_temp 别名 */
		if (strcmp(fc_newRelation->schemaname, "pg_temp") == 0)
		{
			/* 初始化临时命名空间 */
			fc_AccessTempTableNamespace(false);
			return myTempNamespace;
		}
		/* 使用给定的确切架构 */
		fc_namespaceId = get_namespace_oid(fc_newRelation->schemaname, false);
		/* 我们在这里不检查使用权限！ */
	}
	else if (fc_newRelation->relpersistence == RELPERSISTENCE_TEMP)
	{
		/* 初始化临时命名空间 */
		fc_AccessTempTableNamespace(false);
		return myTempNamespace;
	}
	else
	{
		/* 使用默认创建命名空间 */
		fc_recomputeNamespacePath();
		if (activeTempCreationPending)
		{
			/* 需要初始化临时命名空间 */
			fc_AccessTempTableNamespace(true);
			return myTempNamespace;
		}
		fc_namespaceId = activeCreationNamespace;
		if (!OidIsValid(fc_namespaceId))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_SCHEMA),
					 errmsg("no schema has been selected to create in")));
	}

	/* 注意：调用者将适时检查创建权限 */

	return fc_namespaceId;
}

/*
 * RangeVarGetAndCheckCreationNamespace
 *
 * 此函数返回应在指定名称的命名空间中创建的新关系的 OID。
 * 如果用户没有目标命名空间的创建权限，则此函数将发出错误。
 *
 * 如果非空，*existing_relation_id 设置为该命名空间中任何已存在
 * 的具有相同名称的关系的 OID，或者如果不存在这样的关系则设置为 InvalidOid。
 *
 * 如果 lockmode != NoLock，则对已存在的关系（如果有）获取指定的锁定模式，
 * 但前提是当前用户拥有目标关系。
 * 然而，如果 lockmode != NoLock 并且用户不拥有目标
 * 关系，我们将抛出错误，因为我们不能尝试锁定用户没有权限的关系。
 *
 * 作为副作用，此函数获取目标命名空间的 AccessShareLock。
 * 如果没有这个，命名空间可能在我们的事务提交之前被删除，
 * 留下 relnamespace 指向一个不再存在的命名空间的关系。
 *
 * 作为进一步的副作用，如果所选命名空间是临时命名空间，
 * 我们将 RangeVar 标记为 RELPERSISTENCE_TEMP。
 */
Oid RangeVarGetAndCheckCreationNamespace(RangeVar *fc_relation,
									 LOCKMODE fc_lockmode,
									 Oid *fc_existing_relation_id)
{
	uint64		fc_inval_count;
	Oid			fc_relid;
	Oid			fc_oldrelid = InvalidOid;
	Oid			fc_nspid;
	Oid			fc_oldnspid = InvalidOid;
	bool		fc_retry = false;

	/*
	 * 我们检查目录名称，然后忽略它。
	 */
	if (fc_relation->catalogname)
	{
		if (strcmp(fc_relation->catalogname, get_database_name(MyDatabaseId)) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
							fc_relation->catalogname, fc_relation->schemaname,
							fc_relation->relname)));
	}

	/*
	 * 与 RangeVarGetRelidExtended() 中一样，我们通过跟踪在名称查找和锁获取期间
	 * 是否处理了任何无效消息来防范并发 DDL 操作。
	 * 有关此逻辑的更详细说明，请参见该函数中的注释。
	 */
	for (;;)
	{
		AclResult	fc_aclresult;

		fc_inval_count = SharedInvalidMessageCounter;

		/* 查找创建命名空间并检查现有关系。 */
		fc_nspid = RangeVarGetCreationNamespace(fc_relation);
		Assert(OidIsValid(fc_nspid));
		if (fc_existing_relation_id != NULL)
			fc_relid = get_relname_relid(fc_relation->relname, fc_nspid);
		else
			fc_relid = InvalidOid;

		/*
		 * 在引导处理模式下，我们不考虑权限或
		 * 锁定。权限可能尚未生效，而锁定是
		 * 不必要的。
		 */
		if (IsBootstrapProcessingMode())
			break;

		/* 检查命名空间权限。 */
		fc_aclresult = pg_namespace_aclcheck(fc_nspid, GetUserId(), ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
						   get_namespace_name(fc_nspid));

		if (fc_retry)
		{
			/* 如果没有变化，我们完成了。 */
			if (fc_relid == fc_oldrelid && fc_nspid == fc_oldnspid)
				break;
			/* 如果创建命名空间已更改，放弃旧锁定。 */
			if (fc_nspid != fc_oldnspid)
				UnlockDatabaseObject(NamespaceRelationId, fc_oldnspid, 0,
									 AccessShareLock);
			/* 如果名称指向不同的东西，放弃旧锁定。 */
			if (fc_relid != fc_oldrelid && OidIsValid(fc_oldrelid) && fc_lockmode != NoLock)
				UnlockRelationOid(fc_oldrelid, fc_lockmode);
		}

		/* 锁定命名空间。 */
		if (fc_nspid != fc_oldnspid)
			LockDatabaseObject(NamespaceRelationId, fc_nspid, 0, AccessShareLock);

		/* 如果需要且我们有权限，锁定关系。 */
		if (fc_lockmode != NoLock && OidIsValid(fc_relid))
		{
			if (!pg_class_ownercheck(fc_relid, GetUserId()))
				aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relid)),
							   fc_relation->relname);
			if (fc_relid != fc_oldrelid)
				LockRelationOid(fc_relid, fc_lockmode);
		}

		/* 如果没有处理无效消息，我们完成了！ */
		if (fc_inval_count == SharedInvalidMessageCounter)
			break;

		/* 可能发生了一些变化，所以重新检查我们的工作。 */
		fc_retry = true;
		fc_oldrelid = fc_relid;
		fc_oldnspid = fc_nspid;
	}

	RangeVarAdjustRelationPersistence(fc_relation, fc_nspid);
	if (fc_existing_relation_id != NULL)
		*fc_existing_relation_id = fc_relid;
	return fc_nspid;
}

/*
 * 根据创建命名空间调整即将创建的关系的 relpersistence，
 * 并对无效组合抛出错误。
 */
void RangeVarAdjustRelationPersistence(RangeVar *fc_newRelation, Oid fc_nspid)
{
	switch (fc_newRelation->relpersistence)
	{
		case RELPERSISTENCE_TEMP:
			if (!isTempOrTempToastNamespace(fc_nspid))
			{
				if (isAnyTempNamespace(fc_nspid))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
							 errmsg("cannot create relations in temporary schemas of other sessions")));
				else
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
							 errmsg("cannot create temporary relation in non-temporary schema")));
			}
			break;
		case RELPERSISTENCE_PERMANENT:
			if (isTempOrTempToastNamespace(fc_nspid))
				fc_newRelation->relpersistence = RELPERSISTENCE_TEMP;
			else if (isAnyTempNamespace(fc_nspid))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("cannot create relations in temporary schemas of other sessions")));
			break;
		default:
			if (isAnyTempNamespace(fc_nspid))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("only temporary relations may be created in temporary schemas")));
	}
}


/*
 * RelnameGetRelid
 *		尝试解析一个不合格的关系名称。
 *		如果在搜索路径中找到关系，则返回OID，否则返回InvalidOid。
 */
Oid RelnameGetRelid(const char *fc_relname)
{
	Oid			fc_relid;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		fc_relid = get_relname_relid(fc_relname, fc_namespaceId);
		if (OidIsValid(fc_relid))
			return fc_relid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}


/*
 * RelationIsVisible
 *		确定一个关系（通过OID识别）是否在当前搜索路径中可见。
 *		可见意味着“通过搜索不合格的关系名称将被找到”。
 */
bool RelationIsVisible(Oid fc_relid)
{
	HeapTuple	fc_reltup;
	Form_pg_class fc_relform;
	Oid			fc_relnamespace;
	bool		fc_visible;

	fc_reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_reltup))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	fc_relform = (Form_pg_class) GETSTRUCT(fc_reltup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_relnamespace = fc_relform->relnamespace;
	if (fc_relnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_relnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它仍然可能不可见；它可能被路径中同名的另一个关系隐藏。因此我们必须进行慢速检查以查找冲突关系。
		 */
		char	   *fc_relname = NameStr(fc_relform->relname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == fc_relnamespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (OidIsValid(get_relname_relid(fc_relname, fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_reltup);

	return fc_visible;
}


/*
 * TypenameGetTypid
 *		用于二进制兼容性的包装器。
 */
Oid TypenameGetTypid(const char *fc_typname)
{
	return TypenameGetTypidExtended(fc_typname, true);
}

/*
 * TypenameGetTypidExtended
 *		尝试解析未限定的数据类型名称。
 *		如果在搜索路径中找到类型，则返回OID，否则返回InvalidOid。
 *
 * 这本质上与RelnameGetRelid相同。
 */
Oid TypenameGetTypidExtended(const char *fc_typname, bool fc_temp_ok)
{
	Oid			fc_typid;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (!fc_temp_ok && fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
								PointerGetDatum(fc_typname),
								ObjectIdGetDatum(fc_namespaceId));
		if (OidIsValid(fc_typid))
			return fc_typid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * TypeIsVisible
 *		确定一个类型（通过OID识别）是否在当前搜索路径中可见。可见意味着“通过搜索未限定的类型名称可以找到”。
 */
bool TypeIsVisible(Oid fc_typid)
{
	HeapTuple	fc_typtup;
	Form_pg_type fc_typform;
	Oid			fc_typnamespace;
	bool		fc_visible;

	fc_typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typid));
	if (!HeapTupleIsValid(fc_typtup))
		elog(ERROR, "cache lookup failed for type %u", fc_typid);
	fc_typform = (Form_pg_type) GETSTRUCT(fc_typtup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_typnamespace = fc_typform->typnamespace;
	if (fc_typnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_typnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它仍然可能不可见；它可能被路径中同名的另一个类型隐藏。因此我们必须进行慢速检查以查找冲突类型。
		 */
		char	   *fc_typname = NameStr(fc_typform->typname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == fc_typnamespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(TYPENAMENSP,
									  PointerGetDatum(fc_typname),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_typtup);

	return fc_visible;
}



/*
 * FuncnameGetCandidates
 * 给定一个可能合格的函数名和参数计数，检索可能匹配的列表。
 *
 * 如果 nargs 为 -1，我们返回所有与给定名称匹配的函数， 
 * 无论参数计数如何。 （在这种情况下，argnames 必须为 NIL， 
 * expand_variadic 和 expand_defaults 必须为 false。）
 *
 * 如果 argnames 不是 NIL，我们考虑的是命名或混合表示法调用， 
 * 只有拥有所有列出参数名称的函数将被返回。 
 * （我们假设 length(argnames) <= nargs，并且所有传入的名称都是 
 * 不同的。） 返回的结构将包含一个 argnumbers 数组， 
 * 显示每个逻辑参数位置的实际参数索引。
 *
 * 如果 expand_variadic 为 true，则将检索具有相同数量或更少参数的可变参数 
 * 函数， 可变参数和任何额外参数位置将填写可变元素类型。 
 * 返回结构中的 nvargs 设置为此类参数的数量。
 * 如果 expand_variadic 为 false，则可变参数不被特殊处理， 
 * 返回的 nvargs 将始终为零。
 *
 * 如果 expand_defaults 为 true，则可以在插入默认参数值后 
 * 匹配的函数也将被检索。在这种情况下，返回的结构可能具有 
 * nargs > 传入的 nargs，ndargs 设置为附加参数的数量 
 * （可以从函数的 proargdefaults 条目检索）。
 *
 * 如果 include_out_arguments 为 true，则 OUT 模式参数被视为
 * 包含在参数列表中。 其类型包括在返回的数组中， 
 * argnumbers 是在 proallargtypes 中的索引，而不是 proargtypes。 
 * 我们还将 nominalnargs 设置为 proallargtypes 的长度而不是 proargtypes 的长度。 
 * 否则，OUT 模式参数将被忽略。
 *
 * 在同一个列表条目中，nvargs 和 ndargs 不可能同时非零， 
 * 因为默认插入允许与多个 nargs 参数的函数匹配，同时可变参数转换要求 
 * 相同数量或更少。
 *
 * 当 argnames 不是 NIL 时，返回的 args[] 类型数组并不是按照 
 * 函数声明的顺序，而是按照调用的顺序： 
 * 首先是任何位置参数，然后是命名参数，然后是默认参数（如果需要并且 
 * 被 expand_defaults 允许）。 argnumbers[] 数组可以用来将其映射回 
 * 目录信息。 argnumbers[k] 设置为第 k 个调用参数的 proargtypes 或 
 * proallargtypes 索引。
 *
 * 如果函数名称是合格的，我们搜索单个命名空间，否则搜索 
 * 搜索路径中的所有命名空间。在多命名空间情况下， 
 * 我们安排早期命名空间中的条目掩盖后期命名空间中的相同条目。
 *
 * 在扩展可变参数时，我们安排非可变函数掩盖
 * 可变函数，如果扩展的参数列表是相同的。 然而，不同的可变函数间 
 * 仍然可能存在冲突。
 *
 * 可以保证返回列表永远不会包含多个具有相同参数列表的条目。 
 * 当 expand_defaults 为 true 时，条目可以具有超过 nargs 的位置， 
 * 但我们仍然保证它们在前 nargs 个位置上是不同的。 
 * 然而，如果 argnames 不是 NIL 或者 expand_variadic 或 
 * expand_defaults 有一个为 true，可能会有多个候选函数扩展为相同的 
 * 参数列表。 我们不会在这里抛出错误，而是通过返回一个 
 * oid = 0 的单一条目来报告这种情况，表示一组此类冲突候选项。 
 * 调用者可能最终会丢弃这样的条目，但如果选择了这样的条目， 
 * 应该像调用是模糊一样做出反应。
 *
 * 如果 missing_ok 为 true，当名称是带有不存在的模式的架构限定时，会返回 
 * 空列表 (NULL)。同样，如果因其他原因没有找到候选项。
 */
FuncCandidateList FuncnameGetCandidates(List *fc_names, int fc_nargs, List *fc_argnames,
					  bool fc_expand_variadic, bool fc_expand_defaults,
					  bool fc_include_out_arguments, bool fc_missing_ok)
{
	FuncCandidateList fc_resultList = NULL;
	bool		fc_any_special = false;
	char	   *fc_schemaname;
	char	   *fc_funcname;
	Oid			fc_namespaceId;
	CatCList   *fc_catlist;
	int			fc_i;

	/* 检查调用者错误 */
	Assert(fc_nargs >= 0 || !(fc_expand_variadic | fc_expand_defaults));

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_funcname);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (!OidIsValid(fc_namespaceId))
			return NULL;
	}
	else
	{
		/* 标志以指示我们需要命名空间搜索 */
		fc_namespaceId = InvalidOid;
		fc_recomputeNamespacePath();
	}

	/* 仅按名称搜索系统缓存 */
	fc_catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(fc_funcname));

	for (fc_i = 0; fc_i < fc_catlist->n_members; fc_i++)
	{
		HeapTuple	fc_proctup = &fc_catlist->members[fc_i]->tuple;
		Form_pg_proc fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
		Oid		   *fc_proargtypes = fc_procform->proargtypes.values;
		int			fc_pronargs = fc_procform->pronargs;
		int			fc_effective_nargs;
		int			fc_pathpos = 0;
		bool		fc_variadic;
		bool		fc_use_defaults;
		Oid			fc_va_elem_type;
		int		   *fc_argnumbers = NULL;
		FuncCandidateList fc_newResult;

		if (OidIsValid(fc_namespaceId))
		{
			/* 仅考虑指定命名空间中的过程 */
			if (fc_procform->pronamespace != fc_namespaceId)
				continue;
		}
		else
		{
			/*
             * 仅考虑在搜索路径中且不在临时命名空间中的过程。
             */
			ListCell   *fc_nsp;

			foreach(fc_nsp, activeSearchPath)
			{
				if (fc_procform->pronamespace == lfirst_oid(fc_nsp) &&
					fc_procform->pronamespace != myTempNamespace)
					break;
				fc_pathpos++;
			}
			if (fc_nsp == NULL)
				continue;		/* 过程不在搜索路径中 */
		}

		/*
         * 如果我们被要求匹配 OUT 参数，则使用 
         * proallargtypes 数组（其中包含这些）；否则使用
         * proargtypes（不包括这些）。 当然，如果 proallargtypes 为 null，
         * 我们始终使用 proargtypes。
         */
		if (fc_include_out_arguments)
		{
			Datum		fc_proallargtypes;
			bool		fc_isNull;

			fc_proallargtypes = SysCacheGetAttr(PROCNAMEARGSNSP, fc_proctup,
											 Anum_pg_proc_proallargtypes,
											 &fc_isNull);
			if (!fc_isNull)
			{
				ArrayType  *fc_arr = DatumGetArrayTypeP(fc_proallargtypes);

				fc_pronargs = ARR_DIMS(fc_arr)[0];
				if (ARR_NDIM(fc_arr) != 1 ||
					fc_pronargs < 0 ||
					ARR_HASNULL(fc_arr) ||
					ARR_ELEMTYPE(fc_arr) != OIDOID)
					elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
				Assert(fc_pronargs >= fc_procform->pronargs);
				fc_proargtypes = (Oid *) ARR_DATA_PTR(fc_arr);
			}
		}

		if (fc_argnames != NIL)
		{
			/*
             * 调用使用命名或混合表示法
             *
             * 仅当 expand_variadic 关闭时，命名或混合表示法才能 
             * 匹配可变函数；否则无法匹配 
             * 从可变数组扩展的假定无名参数。
             */
			if (OidIsValid(fc_procform->provariadic) && fc_expand_variadic)
				continue;
			fc_va_elem_type = InvalidOid;
			fc_variadic = false;

			/*
             * 检查参数计数。
             */
			Assert(fc_nargs >= 0); /* -1 不支持 argnames */

			if (fc_pronargs > fc_nargs && fc_expand_defaults)
			{
				
/* 忽略如果默认表达式不足 */
				if (fc_nargs + fc_procform->pronargdefaults < fc_pronargs)
					continue;
				fc_use_defaults = true;
			}
			else
				fc_use_defaults = false;

			/* 忽略如果它与请求的参数数量不匹配 */
			if (fc_pronargs != fc_nargs && !fc_use_defaults)
				continue;

			/* 检查参数名称匹配，生成位置映射 */
			if (!fc_MatchNamedCall(fc_proctup, fc_nargs, fc_argnames,
								fc_include_out_arguments, fc_pronargs,
								&fc_argnumbers))
				continue;

			/* 命名参数匹配始终是“特殊的” */
			fc_any_special = true;
		}
		else
		{
			/*
			 * 调用使用位置符号
			 *
			 * 检查函数是否为可变参数，如果是，获取可变元素类型。
			 * 如果 expand_variadic 为 false，我们应该忽略
			 * 可变性。
			 */
			if (fc_pronargs <= fc_nargs && fc_expand_variadic)
			{
				fc_va_elem_type = fc_procform->provariadic;
				fc_variadic = OidIsValid(fc_va_elem_type);
				fc_any_special |= fc_variadic;
			}
			else
			{
				fc_va_elem_type = InvalidOid;
				fc_variadic = false;
			}

			/*
			 * 检查函数是否可以通过使用参数默认值来匹配。
			 */
			if (fc_pronargs > fc_nargs && fc_expand_defaults)
			{
				
/* 忽略如果默认表达式不足 */
				if (fc_nargs + fc_procform->pronargdefaults < fc_pronargs)
					continue;
				fc_use_defaults = true;
				fc_any_special = true;
			}
			else
				fc_use_defaults = false;

			/* 忽略如果它与请求的参数数量不匹配 */
			if (fc_nargs >= 0 && fc_pronargs != fc_nargs && !fc_variadic && !fc_use_defaults)
				continue;
		}

		/*
		 * 我们必须计算有效的参数列表，以便我们可以轻松
		 * 将其与早期结果进行比较。 如果它被早期结果
		 * 遮蔽，我们会浪费一个 palloc 循环，但这实际上是一个相当不频繁
		 * 的情况，所以不值得担心。
		 */
		fc_effective_nargs = Max(fc_pronargs, fc_nargs);
		fc_newResult = (FuncCandidateList)
			palloc(offsetof(struct _FuncCandidateList, args) +
				   fc_effective_nargs * sizeof(Oid));
		fc_newResult->pathpos = fc_pathpos;
		fc_newResult->oid = fc_procform->oid;
		fc_newResult->nominalnargs = fc_pronargs;
		fc_newResult->nargs = fc_effective_nargs;
		fc_newResult->argnumbers = fc_argnumbers;
		if (fc_argnumbers)
		{
			/* 将参数类型重新排序为调用的逻辑顺序 */
			int			fc_i;

			for (fc_i = 0; fc_i < fc_pronargs; fc_i++)
				fc_newResult->args[fc_i] = fc_proargtypes[fc_argnumbers[fc_i]];
		}
		else
		{
			/* 简单的位置案例，只需按原样复制 proargtypes */
			memcpy(fc_newResult->args, fc_proargtypes, fc_pronargs * sizeof(Oid));
		}
		if (fc_variadic)
		{
			int			fc_i;

			fc_newResult->nvargs = fc_effective_nargs - fc_pronargs + 1;
			/* 将可变参数扩展为 N 份元素类型的副本 */
			for (fc_i = fc_pronargs - 1; fc_i < fc_effective_nargs; fc_i++)
				fc_newResult->args[fc_i] = fc_va_elem_type;
		}
		else
			fc_newResult->nvargs = 0;
		fc_newResult->ndargs = fc_use_defaults ? fc_pronargs - fc_nargs : 0;

		/*
		 * 它是否具有我们已经接受的某些内容的相同参数？
		 * 如果是这样，决定如何做以避免返回重复的参数
		 * 列表。如果没有进行特殊（命名、可变或默认）匹配，则可以跳过对单命名空间情况的检查，
		 * 因为在这种情况下，pg_proc 上的唯一索引保证所有匹配具有
		 * 不同的参数列表。
		 */
		if (fc_resultList != NULL &&
			(fc_any_special || !OidIsValid(fc_namespaceId)))
		{
			/*
			 * 如果我们从 SearchSysCacheList 得到了一个有序列表（正常
			 * 情况），那么任何冲突的过程必须立即与这个
			 * 过程相邻，因此我们只需要查看最新的结果
			 * 项目。如果我们有一个无序列表，则必须扫描整个
			 * 结果列表。此外，如果当前候选项或任何
			 * 先前候选项是特殊匹配，我们不能假设
			 * 冲突是相邻的。
			 *
			 * 在决定什么是匹配时，我们忽略默认参数。
			 */
			FuncCandidateList fc_prevResult;

			if (fc_catlist->ordered && !fc_any_special)
			{
				/* 如果 !any_special ，ndargs 必须为 0 */
				if (fc_effective_nargs == fc_resultList->nargs &&
					memcmp(fc_newResult->args,
						   fc_resultList->args,
						   fc_effective_nargs * sizeof(Oid)) == 0)
					fc_prevResult = fc_resultList;
				else
					fc_prevResult = NULL;
			}
			else
			{
				int			fc_cmp_nargs = fc_newResult->nargs - fc_newResult->ndargs;

				for (fc_prevResult = fc_resultList;
					 fc_prevResult;
					 fc_prevResult = fc_prevResult->next)
				{
					if (fc_cmp_nargs == fc_prevResult->nargs - fc_prevResult->ndargs &&
						memcmp(fc_newResult->args,
							   fc_prevResult->args,
							   fc_cmp_nargs * sizeof(Oid)) == 0)
						break;
				}
			}

			if (fc_prevResult)
			{
				/*
				 * 我们与先前的结果匹配。决定保留哪一个，
				 * 或者如果我们无法决定则标记为模棱两可。这里的
				 * 逻辑是，偏好 > 0 意味着更倾向于旧结果，
				 * 偏好 < 0 意味着更倾向于新结果，偏好 = 0 意味着
				 * 模棱两可。
				 */
				int			fc_preference;

				if (fc_pathpos != fc_prevResult->pathpos)
				{
					/*
					 * 优先选择在搜索路径中较早的那个。
					 */
					fc_preference = fc_pathpos - fc_prevResult->pathpos;
				}
				else if (fc_variadic && fc_prevResult->nvargs == 0)
				{
					/*
					 * 在可变参数函数中，例如，我们可能有
					 * foo(numeric) 和 foo(variadic numeric[]) 在
					 * 同一个命名空间中；如果是这样，我们优先选择非可变参数匹配
					 * 出于效率原因。
					 */
					fc_preference = 1;
				}
				else if (!fc_variadic && fc_prevResult->nvargs > 0)
				{
					fc_preference = -1;
				}
				else
				{
					/*----------
					 * 我们无法决定。这可能发生在，例如，
					 * foo(numeric, variadic numeric[]) 和
					 * foo(variadic numeric[]) 在同一个命名空间中，或者
					 * foo(int) 和 foo (int, int default something)
					 * 在同一个命名空间中，或者
					 * foo(a int, b text) 和 foo(b text, a int)
					 * 在同一个命名空间中。
					 *----------
					 */
					fc_preference = 0;
				}

				if (fc_preference > 0)
				{
					/* 保留先前的结果 */
					pfree(fc_newResult);
					continue;
				}
				else if (fc_preference < 0)
				{
					/* 从列表中删除先前的结果 */
					if (fc_prevResult == fc_resultList)
						fc_resultList = fc_prevResult->next;
					else
					{
						FuncCandidateList fc_prevPrevResult;

						for (fc_prevPrevResult = fc_resultList;
							 fc_prevPrevResult;
							 fc_prevPrevResult = fc_prevPrevResult->next)
						{
							if (fc_prevResult == fc_prevPrevResult->next)
							{
								fc_prevPrevResult->next = fc_prevResult->next;
								break;
							}
						}
						Assert(fc_prevPrevResult); /* 确保我们找到了它 */
					}
					pfree(fc_prevResult);
					/* 继续添加 newResult 到列表 */
				}
				else
				{
					/* 将旧结果标记为模棱两可，丢弃新结果 */
					fc_prevResult->oid = InvalidOid;
					pfree(fc_newResult);
					continue;
				}
			}
		}

		/*
		 * 可以将其添加到结果列表中
		 */
		fc_newResult->next = fc_resultList;
		fc_resultList = fc_newResult;
	}

	ReleaseSysCacheList(fc_catlist);

	return fc_resultList;
}

/*
 * MatchNamedCall
 *		给定一个 pg_proc 堆元组和调用参数名称的列表，
 *		检查该函数是否能够匹配该调用。
 *
 * 如果所有提供的参数名称都被
 * 函数接受，在最后一个位置参数之后，并且
 * 所有未提供的参数都有默认值，则调用可以匹配。
 *
 * 如果 include_out_arguments 为真，我们将 OUT 参数视为
 * 包含在参数列表中。pronargs 是我们考虑的参数数量
 * （proargtypes 或 proallargtypes 的长度）。
 *
 * 位置参数的数量是 nargs - list_length(argnames)。
 * 请注意，调用者已经对参数数量进行了基本检查。
 *
 * 在匹配时，返回 true，并填充 *argnumbers 以显示
 * 从调用参数位置到实际函数参数
 * 编号的映射的 palloc'd 数组。默认参数包含在此映射中，
 * 在最后一个提供的参数之后的位置。
 */
static bool fc_MatchNamedCall(HeapTuple fc_proctup, int fc_nargs, List *fc_argnames,
			   bool fc_include_out_arguments, int fc_pronargs,
			   int **fc_argnumbers)
{
	Form_pg_proc fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
	int			fc_numposargs = fc_nargs - list_length(fc_argnames);
	int			fc_pronallargs;
	Oid		   *fc_p_argtypes;
	char	  **fc_p_argnames;
	char	   *fc_p_argmodes;
	bool		fc_arggiven[FUNC_MAX_ARGS];
	bool		fc_isnull;
	int			fc_ap;				/* 调用参数位置 */
	int			fc_pp;				/* proargs 位置 */
	ListCell   *fc_lc;

	Assert(fc_argnames != NIL);
	Assert(fc_numposargs >= 0);
	Assert(fc_nargs <= fc_pronargs);

	/* 如果其 proargnames 为 null，则忽略此函数 */
	(void) SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_proargnames,
						   &fc_isnull);
	if (fc_isnull)
		return false;

	/* 好的，提取参数名称和类型 */
	fc_pronallargs = get_func_arg_info(fc_proctup,
									&fc_p_argtypes, &fc_p_argnames, &fc_p_argmodes);
	Assert(fc_p_argnames != NULL);

	Assert(fc_include_out_arguments ? (fc_pronargs == fc_pronallargs) : (fc_pronargs <= fc_pronallargs));

	/* 初始化匹配状态 */
	*fc_argnumbers = (int *) palloc(fc_pronargs * sizeof(int));
	memset(fc_arggiven, false, fc_pronargs * sizeof(bool));

	/* 在命名参数之前有 numposargs 个位置参数 */
	for (fc_ap = 0; fc_ap < fc_numposargs; fc_ap++)
	{
		(*fc_argnumbers)[fc_ap] = fc_ap;
		fc_arggiven[fc_ap] = true;
	}

	/* 现在检查命名参数 */
	foreach(fc_lc, fc_argnames)
	{
		char	   *fc_argname = (char *) lfirst(fc_lc);
		bool		fc_found;
		int			fc_i;

		fc_pp = 0;
		fc_found = false;
		for (fc_i = 0; fc_i < fc_pronallargs; fc_i++)
		{
			/* 仅考虑输入参数，除非包含外部参数 */
			if (!fc_include_out_arguments &&
				fc_p_argmodes &&
				(fc_p_argmodes[fc_i] != FUNC_PARAM_IN &&
				 fc_p_argmodes[fc_i] != FUNC_PARAM_INOUT &&
				 fc_p_argmodes[fc_i] != FUNC_PARAM_VARIADIC))
				continue;
			if (fc_p_argnames[fc_i] && strcmp(fc_p_argnames[fc_i], fc_argname) == 0)
			{
				/* 如果参数名称与位置参数匹配则失败 */
				if (fc_arggiven[fc_pp])
					return false;
				fc_arggiven[fc_pp] = true;
				(*fc_argnumbers)[fc_ap] = fc_pp;
				fc_found = true;
				break;
			}
			/* 仅对考虑的参数增加 pp */
			fc_pp++;
		}
		/* 如果名称不在 proargnames 中，则失败 */
		if (!fc_found)
			return false;
		fc_ap++;
	}

	Assert(fc_ap == fc_nargs);		/* 处理所有实际参数 */

	/* 检查默认参数 */
	if (fc_nargs < fc_pronargs)
	{
		int			fc_first_arg_with_default = fc_pronargs - fc_procform->pronargdefaults;

		for (fc_pp = fc_numposargs; fc_pp < fc_pronargs; fc_pp++)
		{
			if (fc_arggiven[fc_pp])
				continue;
			/* 如果未给出参数且没有可用的默认值则失败 */
			if (fc_pp < fc_first_arg_with_default)
				return false;
			(*fc_argnumbers)[fc_ap++] = fc_pp;
		}
	}

	Assert(fc_ap == fc_pronargs);		/* 处理所有函数参数 */

	return true;
}

/*
 * FunctionIsVisible
 *		确定一个函数（通过 OID 识别）是否在当前搜索路径中可见。可见的意思是“通过搜索未限定函数名称并精确匹配参数找到该函数”。
 */
bool FunctionIsVisible(Oid fc_funcid)
{
	HeapTuple	fc_proctup;
	Form_pg_proc fc_procform;
	Oid			fc_pronamespace;
	bool		fc_visible;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		elog(ERROR, "cache lookup failed for function %u", fc_funcid);
	fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_pronamespace = fc_procform->pronamespace;
	if (fc_pronamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_pronamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果它在路径中，但仍然可能不可见；它可能被路径中较早的同名同参数的另一个过程隐藏。因此我们必须进行慢速检查，以查看这是否是通过 FuncnameGetCandidates 找到的相同过程。
 */
		char	   *fc_proname = NameStr(fc_procform->proname);
		int			fc_nargs = fc_procform->pronargs;
		FuncCandidateList fc_clist;

		fc_visible = false;

		fc_clist = FuncnameGetCandidates(list_make1(makeString(fc_proname)),
									  fc_nargs, NIL, false, false, false, false);

		for (; fc_clist; fc_clist = fc_clist->next)
		{
			if (memcmp(fc_clist->args, fc_procform->proargtypes.values,
					   fc_nargs * sizeof(Oid)) == 0)
			{
				/* 找到了预期的条目；它是正确的过程吗？ */
				fc_visible = (fc_clist->oid == fc_funcid);
				break;
			}
		}
	}

	ReleaseSysCache(fc_proctup);

	return fc_visible;
}


/*
 * OpernameGetOprid
 *		给定一个可能限定的操作符名称和精确的输入数据类型，查找操作符。如果未找到，则返回 InvalidOid。
 *
 * 对于前缀操作符，传递 oprleft = InvalidOid。
 *
 * 如果操作符名称没有模式限定，则在当前命名空间搜索路径中查找。如果名称是模式限定的，并且给定的模式不存在，则返回 InvalidOid。
 */
Oid OpernameGetOprid(List *fc_names, Oid fc_oprleft, Oid fc_oprright)
{
	char	   *fc_schemaname;
	char	   *fc_opername;
	CatCList   *fc_catlist;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_opername);

	if (fc_schemaname)
	{
		/* 仅在给定的确切模式中搜索 */
		Oid			fc_namespaceId;

		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, true);
		if (OidIsValid(fc_namespaceId))
		{
			HeapTuple	fc_opertup;

			fc_opertup = SearchSysCache4(OPERNAMENSP,
									  CStringGetDatum(fc_opername),
									  ObjectIdGetDatum(fc_oprleft),
									  ObjectIdGetDatum(fc_oprright),
									  ObjectIdGetDatum(fc_namespaceId));
			if (HeapTupleIsValid(fc_opertup))
			{
				Form_pg_operator fc_operclass = (Form_pg_operator) GETSTRUCT(fc_opertup);
				Oid			fc_result = fc_operclass->oid;

				ReleaseSysCache(fc_opertup);
				return fc_result;
			}
		}

		return InvalidOid;
	}

	/* 根据名称和参数类型在系统缓存中搜索 */
	fc_catlist = SearchSysCacheList3(OPERNAMENSP,
								  CStringGetDatum(fc_opername),
								  ObjectIdGetDatum(fc_oprleft),
								  ObjectIdGetDatum(fc_oprright));

	if (fc_catlist->n_members == 0)
	{
		/* 没有希望，早早退出 */
		ReleaseSysCacheList(fc_catlist);
		return InvalidOid;
	}

	/*
	 * 如果有多个，我们必须找到在搜索路径中第一个的列表成员。这个两层嵌套的循环看起来很丑，但实际上通常情况下，catlist 成员的数量应该很少。
 */
	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);
		int			fc_i;

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		for (fc_i = 0; fc_i < fc_catlist->n_members; fc_i++)
		{
			HeapTuple	fc_opertup = &fc_catlist->members[fc_i]->tuple;
			Form_pg_operator fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);

			if (fc_operform->oprnamespace == fc_namespaceId)
			{
				Oid			fc_result = fc_operform->oid;

				ReleaseSysCacheList(fc_catlist);
				return fc_result;
			}
		}
	}

	ReleaseSysCacheList(fc_catlist);
	return InvalidOid;
}

/*
 * OpernameGetCandidates
 *		给定一个可能限定的操作符名称和操作符类型，检索可能匹配的列表。
 *
 * 如果 oprkind 是 '\0'，我们返回所有匹配给定名称的操作符，无论参数如何。
 *
 * 如果操作符名称被限定，我们只搜索单个命名空间；否则搜索搜索路径中的所有命名空间。返回的列表不会包含参数列表相同的多个条目——在多个命名空间的情况下，我们安排早期命名空间中的条目掩盖后期命名空间中的相同条目。
 *
 * 返回的项始终具有两个 args[] 条目——第一个对于前缀 oprkind 将是 InvalidOid。nargs 也始终是 2。
 */
FuncCandidateList OpernameGetCandidates(List *fc_names, char fc_oprkind, bool fc_missing_schema_ok)
{
	FuncCandidateList fc_resultList = NULL;
	char	   *fc_resultSpace = NULL;
	int			fc_nextResult = 0;
	char	   *fc_schemaname;
	char	   *fc_opername;
	Oid			fc_namespaceId;
	CatCList   *fc_catlist;
	int			fc_i;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_opername);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_schema_ok);
		if (fc_missing_schema_ok && !OidIsValid(fc_namespaceId))
			return NULL;
	}
	else
	{
		/* 标志以指示我们需要命名空间搜索 */
		fc_namespaceId = InvalidOid;
		fc_recomputeNamespacePath();
	}

	/* 仅按名称搜索系统缓存 */
	fc_catlist = SearchSysCacheList1(OPERNAMENSP, CStringGetDatum(fc_opername));

	/*
	 * 在典型场景中，通过 catcache 搜索找到的大多数（如果不是全部）操作符最终都会被返回；对此，常见操作符名称如 '=' 或 '+' 可能会有很多。为了减少在 palloc 中花费的时间，我们将结果空间分配为足够大的数组，以容纳所有操作符。该例程的最初编码对每个操作符进行了单独的 palloc，但分析显示，palloc 使用了不合理的大量解析时间。
 */
#define SPACE_PER_OP MAXALIGN(offsetof(struct _FuncCandidateList, args) + \
							  2 * sizeof(Oid))

	if (fc_catlist->n_members > 0)
		fc_resultSpace = palloc(fc_catlist->n_members * SPACE_PER_OP);

	for (fc_i = 0; fc_i < fc_catlist->n_members; fc_i++)
	{
		HeapTuple	fc_opertup = &fc_catlist->members[fc_i]->tuple;
		Form_pg_operator fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
		int			fc_pathpos = 0;
		FuncCandidateList fc_newResult;

		/* 忽略请求的特定类型的错误类型操作符 */
		if (fc_oprkind && fc_operform->oprkind != fc_oprkind)
			continue;

		if (OidIsValid(fc_namespaceId))
		{
			/* 仅考虑指定命名空间中的操作符 */
			if (fc_operform->oprnamespace != fc_namespaceId)
				continue;
			/* 无需检查参数，它们必须都是不同的 */
		}
		else
		{
			/*
			 * 仅考虑在搜索路径中且不在临时命名空间中的操作符。
 */
			ListCell   *fc_nsp;

			foreach(fc_nsp, activeSearchPath)
			{
				if (fc_operform->oprnamespace == lfirst_oid(fc_nsp) &&
					fc_operform->oprnamespace != myTempNamespace)
					break;
				fc_pathpos++;
			}
			if (fc_nsp == NULL)
				continue;		/* 操作符不在搜索路径中 */

			/*
			 * 好吧，它在搜索路径中，但它的参数是否与我们已经接受的某个参数相同？如果是这样，保留搜索路径中出现较早的那个。
			 *
			 * 如果我们从 SearchSysCacheList 获得一个有序列表（正常情况），则任何冲突的操作符必须立即与该项相邻，因此我们只需查看最新的结果项。如果我们有一个无序列表，则必须扫描整个结果列表。
 */
			if (fc_resultList)
			{
				FuncCandidateList fc_prevResult;

				if (fc_catlist->ordered)
				{
					if (fc_operform->oprleft == fc_resultList->args[0] &&
						fc_operform->oprright == fc_resultList->args[1])
						fc_prevResult = fc_resultList;
					else
						fc_prevResult = NULL;
				}
				else
				{
					for (fc_prevResult = fc_resultList;
						 fc_prevResult;
						 fc_prevResult = fc_prevResult->next)
					{
						if (fc_operform->oprleft == fc_prevResult->args[0] &&
							fc_operform->oprright == fc_prevResult->args[1])
							break;
					}
				}
				if (fc_prevResult)
				{
					/* 我们与先前的结果有匹配 */
					Assert(fc_pathpos != fc_prevResult->pathpos);
					if (fc_pathpos > fc_prevResult->pathpos)
						continue;	/* 保留先前的结果 */
					/* 替换先前的结果 */
					fc_prevResult->pathpos = fc_pathpos;
					fc_prevResult->oid = fc_operform->oid;
					continue;	/* 参数当然是相同的 */
				}
			}
		}

		/*
		 * 可以将其添加到结果列表中
		 */
		fc_newResult = (FuncCandidateList) (fc_resultSpace + fc_nextResult);
		fc_nextResult += SPACE_PER_OP;

		fc_newResult->pathpos = fc_pathpos;
		fc_newResult->oid = fc_operform->oid;
		fc_newResult->nominalnargs = 2;
		fc_newResult->nargs = 2;
		fc_newResult->nvargs = 0;
		fc_newResult->ndargs = 0;
		fc_newResult->argnumbers = NULL;
		fc_newResult->args[0] = fc_operform->oprleft;
		fc_newResult->args[1] = fc_operform->oprright;
		fc_newResult->next = fc_resultList;
		fc_resultList = fc_newResult;
	}

	ReleaseSysCacheList(fc_catlist);

	return fc_resultList;
}

/*
 * OperatorIsVisible
 *		确定一个操作符（通过 OID 识别）是否在当前搜索路径中可见。可见的意思是“通过搜索未限定操作符名称并精确匹配参数找到该操作符”。
 */
bool OperatorIsVisible(Oid fc_oprid)
{
	HeapTuple	fc_oprtup;
	Form_pg_operator fc_oprform;
	Oid			fc_oprnamespace;
	bool		fc_visible;

	fc_oprtup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_oprid));
	if (!HeapTupleIsValid(fc_oprtup))
		elog(ERROR, "cache lookup failed for operator %u", fc_oprid);
	fc_oprform = (Form_pg_operator) GETSTRUCT(fc_oprtup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_oprnamespace = fc_oprform->oprnamespace;
	if (fc_oprnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_oprnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果它在路径中，它可能仍然不可见；它可能被路径中较早的同名同参数的另一个操作符隐藏。因此我们必须进行慢速检查，以查看这是否是通过 OpernameGetOprid 找到的相同操作符。
 */
		char	   *fc_oprname = NameStr(fc_oprform->oprname);

		fc_visible = (OpernameGetOprid(list_make1(makeString(fc_oprname)),
									fc_oprform->oprleft, fc_oprform->oprright)
				   == fc_oprid);
	}

	ReleaseSysCache(fc_oprtup);

	return fc_visible;
}


/*
 * OpclassnameGetOpcid
 *		尝试解析未限定的索引 opclass 名称。如果在搜索路径中找到 opclass，则返回 OID，否则返回 InvalidOid。
 *
 * 这基本上与 TypenameGetTypid 相同，但是我们必须为索引 AM OID 提供一个额外参数。
 */
Oid OpclassnameGetOpcid(Oid fc_amid, const char *fc_opcname)
{
	Oid			fc_opcid;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_opcid = GetSysCacheOid3(CLAAMNAMENSP, Anum_pg_opclass_oid,
								ObjectIdGetDatum(fc_amid),
								PointerGetDatum(fc_opcname),
								ObjectIdGetDatum(fc_namespaceId));
		if (OidIsValid(fc_opcid))
			return fc_opcid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * OpclassIsVisible
 *		确定一个 opclass（通过 OID 识别）是否在当前搜索路径中可见。可见的意思是“通过搜索未限定的 opclass 名称找到该 opclass”。
 */
bool OpclassIsVisible(Oid fc_opcid)
{
	HeapTuple	fc_opctup;
	Form_pg_opclass fc_opcform;
	Oid			fc_opcnamespace;
	bool		fc_visible;

	fc_opctup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opcid));
	if (!HeapTupleIsValid(fc_opctup))
		elog(ERROR, "cache lookup failed for opclass %u", fc_opcid);
	fc_opcform = (Form_pg_opclass) GETSTRUCT(fc_opctup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_opcnamespace = fc_opcform->opcnamespace;
	if (fc_opcnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_opcnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果它在路径中，它可能仍然不可见；它可能被路径中较早的同名的另一个 opclass 隐藏。因此，我们必须进行慢速检查，以查看该 opclass 是否可以通过 OpclassnameGetOpcid 找到。
 */
		char	   *fc_opcname = NameStr(fc_opcform->opcname);

		fc_visible = (OpclassnameGetOpcid(fc_opcform->opcmethod, fc_opcname) == fc_opcid);
	}

	ReleaseSysCache(fc_opctup);

	return fc_visible;
}


/***************************************************************
 * OpfamilynameGetOpfid
 *		尝试解析未限定的索引 opfamily 名称。
 *		如果在搜索路径中找到 opfamily，返回 OID，否则返回 InvalidOid。
 *
 * 这本质上与 TypenameGetTypid 相同，但我们必须为索引 AM OID 添加
 * 一个额外的参数。
 */
Oid OpfamilynameGetOpfid(Oid fc_amid, const char *fc_opfname)
{
	Oid			fc_opfid;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_opfid = GetSysCacheOid3(OPFAMILYAMNAMENSP, Anum_pg_opfamily_oid,
								ObjectIdGetDatum(fc_amid),
								PointerGetDatum(fc_opfname),
								ObjectIdGetDatum(fc_namespaceId));
		if (OidIsValid(fc_opfid))
			return fc_opfid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * OpfamilyIsVisible
 *		确定一个 opfamily（通过 OID 标识）在当前搜索路径中是否可见。
 *		可见意味着“通过搜索未限定的 opfamily 名称可以找到”。
 */
bool OpfamilyIsVisible(Oid fc_opfid)
{
	HeapTuple	fc_opftup;
	Form_pg_opfamily fc_opfform;
	Oid			fc_opfnamespace;
	bool		fc_visible;

	fc_opftup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfid));
	if (!HeapTupleIsValid(fc_opftup))
		elog(ERROR, "cache lookup failed for opfamily %u", fc_opfid);
	fc_opfform = (Form_pg_opfamily) GETSTRUCT(fc_opftup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_opfnamespace = fc_opfform->opfnamespace;
	if (fc_opfnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_opfnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果它在路径中，它仍然可能不可见；它可能被路径中
		 * 之前的同名 opfamily 隐藏。因此我们必须进行慢速检查，以
		 * 查看这个 opfamily 是否可以通过 OpfamilynameGetOpfid 找到。
		 */
		char	   *fc_opfname = NameStr(fc_opfform->opfname);

		fc_visible = (OpfamilynameGetOpfid(fc_opfform->opfmethod, fc_opfname) == fc_opfid);
	}

	ReleaseSysCache(fc_opftup);

	return fc_visible;
}

/*
 * lookup_collation
 *		如果存在具有给定名称/命名空间的排序规则，并且该排序规则
 *		适用于给定的编码，则返回其 OID。否则返回 InvalidOid。
 */
static Oid fc_lookup_collation(const char *fc_collname, Oid fc_collnamespace, int32 fc_encoding)
{
	Oid			fc_collid;
	HeapTuple	fc_colltup;
	Form_pg_collation fc_collform;

	/* 检查特定编码的条目（精确匹配） */
	fc_collid = GetSysCacheOid3(COLLNAMEENCNSP, Anum_pg_collation_oid,
							 PointerGetDatum(fc_collname),
							 Int32GetDatum(fc_encoding),
							 ObjectIdGetDatum(fc_collnamespace));
	if (OidIsValid(fc_collid))
		return fc_collid;

	/*
	 * 检查任意编码的条目。这需要更多的工作：虽然 libc
	 * 排序规则的 collencoding = -1 可以与所有编码一起工作，
	 * 但是 ICU 排序规则仅与某些编码一起工作，因此在决定匹配之前
	 * 我们必须检查这一点。
	 */
	fc_colltup = SearchSysCache3(COLLNAMEENCNSP,
							  PointerGetDatum(fc_collname),
							  Int32GetDatum(-1),
							  ObjectIdGetDatum(fc_collnamespace));
	if (!HeapTupleIsValid(fc_colltup))
		return InvalidOid;
	fc_collform = (Form_pg_collation) GETSTRUCT(fc_colltup);
	if (fc_collform->collprovider == COLLPROVIDER_ICU)
	{
		if (is_encoding_supported_by_icu(fc_encoding))
			fc_collid = fc_collform->oid;
		else
			fc_collid = InvalidOid;
	}
	else
	{
		fc_collid = fc_collform->oid;
	}
	ReleaseSysCache(fc_colltup);
	return fc_collid;
}

/*
 * CollationGetCollid
 *		尝试解析一个未限定的排序规则名称。
 *		如果在搜索路径中找到排序规则，则返回 OID，否则返回 InvalidOid。
 *
 * 请注意，这只会找到与当前数据库编码兼容的排序规则。
 */
Oid CollationGetCollid(const char *fc_collname)
{
	int32		fc_dbencoding = GetDatabaseEncoding();
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);
		Oid			fc_collid;

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_collid = fc_lookup_collation(fc_collname, fc_namespaceId, fc_dbencoding);
		if (OidIsValid(fc_collid))
			return fc_collid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * CollationIsVisible
 *		确定一个排序规则（由 OID 标识）是否在当前搜索路径中可见。可见的意思是“通过搜索未限定的排序规则名称可以找到”。
 *
 * 请注意，只有与当前数据库编码兼容的排序规则才会被视为可见。
 */
bool CollationIsVisible(Oid fc_collid)
{
	HeapTuple	fc_colltup;
	Form_pg_collation fc_collform;
	Oid			fc_collnamespace;
	bool		fc_visible;

	fc_colltup = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collid));
	if (!HeapTupleIsValid(fc_colltup))
		elog(ERROR, "cache lookup failed for collation %u", fc_collid);
	fc_collform = (Form_pg_collation) GETSTRUCT(fc_colltup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_collnamespace = fc_collform->collnamespace;
	if (fc_collnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_collnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名排序规则隐藏，
		 * 或者可能与当前数据库编码不兼容。因此，我们必须进行缓慢检查以查看该排序规则是否可以通过
		 * CollationGetCollid 找到。
		 */
		char	   *fc_collname = NameStr(fc_collform->collname);

		fc_visible = (CollationGetCollid(fc_collname) == fc_collid);
	}

	ReleaseSysCache(fc_colltup);

	return fc_visible;
}


/*
 * ConversionGetConid
 *		尝试解析一个未限定的转换名称。
 *		如果在搜索路径中找到转换，则返回 OID，否则返回 InvalidOid。
 *
 * 这实际上与 RelnameGetRelid 相同。
 */
Oid ConversionGetConid(const char *fc_conname)
{
	Oid			fc_conid;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_conid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid,
								PointerGetDatum(fc_conname),
								ObjectIdGetDatum(fc_namespaceId));
		if (OidIsValid(fc_conid))
			return fc_conid;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * ConversionIsVisible
 *		确定一个转换（由 OID 标识）是否在当前搜索路径中可见。可见的意思是“通过搜索未限定的转换名称可以找到”。
 */
bool ConversionIsVisible(Oid fc_conid)
{
	HeapTuple	fc_contup;
	Form_pg_conversion fc_conform;
	Oid			fc_connamespace;
	bool		fc_visible;

	fc_contup = SearchSysCache1(CONVOID, ObjectIdGetDatum(fc_conid));
	if (!HeapTupleIsValid(fc_contup))
		elog(ERROR, "cache lookup failed for conversion %u", fc_conid);
	fc_conform = (Form_pg_conversion) GETSTRUCT(fc_contup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_connamespace = fc_conform->connamespace;
	if (fc_connamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_connamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名转换隐藏。
		 * 因此，我们必须进行缓慢检查以查看该转换是否可以通过 ConversionGetConid 找到。
		 */
		char	   *fc_conname = NameStr(fc_conform->conname);

		fc_visible = (ConversionGetConid(fc_conname) == fc_conid);
	}

	ReleaseSysCache(fc_contup);

	return fc_visible;
}

/*
 * get_statistics_object_oid - 通过可能的限定名称查找统计对象
 *
 * 如果未找到，则返回 InvalidOid（如果 missing_ok），否则抛出错误
 */
Oid get_statistics_object_oid(List *fc_names, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_stats_name;
	Oid			fc_namespaceId;
	Oid			fc_stats_oid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_stats_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_stats_oid = InvalidOid;
		else
			fc_stats_oid = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
										PointerGetDatum(fc_stats_name),
										ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */
			fc_stats_oid = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
										PointerGetDatum(fc_stats_name),
										ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_stats_oid))
				break;
		}
	}

	if (!OidIsValid(fc_stats_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("statistics object \"%s\" does not exist",
						NameListToString(fc_names))));

	return fc_stats_oid;
}

/*
 * StatisticsObjIsVisible
 *		确定一个统计对象（由 OID 标识）在当前搜索路径中是否可见。可见的意思是“通过搜索未限定的统计对象名称可以找到”。
 */
bool StatisticsObjIsVisible(Oid fc_relid)
{
	HeapTuple	fc_stxtup;
	Form_pg_statistic_ext fc_stxform;
	Oid			fc_stxnamespace;
	bool		fc_visible;

	fc_stxtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_stxtup))
		elog(ERROR, "cache lookup failed for statistics object %u", fc_relid);
	fc_stxform = (Form_pg_statistic_ext) GETSTRUCT(fc_stxtup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_stxnamespace = fc_stxform->stxnamespace;
	if (fc_stxnamespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_stxnamespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名统计对象隐藏。
		 * 所以我们必须对冲突对象进行缓慢检查。
		 */
		char	   *fc_stxname = NameStr(fc_stxform->stxname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == fc_stxnamespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(STATEXTNAMENSP,
									  PointerGetDatum(fc_stxname),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_stxtup);

	return fc_visible;
}

/*
 * get_ts_parser_oid - 通过可能的限定名称查找 TS 解析器
 *
 * 如果未找到，则返回 InvalidOid（如果 missing_ok），否则抛出错误
 */
Oid get_ts_parser_oid(List *fc_names, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_parser_name;
	Oid			fc_namespaceId;
	Oid			fc_prsoid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_parser_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_prsoid = InvalidOid;
		else
			fc_prsoid = GetSysCacheOid2(TSPARSERNAMENSP, Anum_pg_ts_parser_oid,
									 PointerGetDatum(fc_parser_name),
									 ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_prsoid = GetSysCacheOid2(TSPARSERNAMENSP, Anum_pg_ts_parser_oid,
									 PointerGetDatum(fc_parser_name),
									 ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_prsoid))
				break;
		}
	}

	if (!OidIsValid(fc_prsoid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("text search parser \"%s\" does not exist",
						NameListToString(fc_names))));

	return fc_prsoid;
}

/*
 * TSParserIsVisible
 *		确定一个解析器（由 OID 标识）在当前搜索路径中是否可见。可见的意思是“通过搜索未限定的解析器名称可以找到”。
 */
bool TSParserIsVisible(Oid fc_prsId)
{
	HeapTuple	fc_tup;
	Form_pg_ts_parser fc_form;
	Oid			fc_namespace;
	bool		fc_visible;

	fc_tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(fc_prsId));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search parser %u", fc_prsId);
	fc_form = (Form_pg_ts_parser) GETSTRUCT(fc_tup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_namespace = fc_form->prsnamespace;
	if (fc_namespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_namespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名解析器隐藏。因此
		 * 我们必须对冲突解析器进行缓慢检查。
		 */
		char	   *fc_name = NameStr(fc_form->prsname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			if (fc_namespaceId == fc_namespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(TSPARSERNAMENSP,
									  PointerGetDatum(fc_name),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_tup);

	return fc_visible;
}

/*
 * get_ts_dict_oid - 通过可能的限定名称查找 TS 字典
 *
 * 如果未找到，则返回 InvalidOid（如果 missing_ok），否则抛出错误
 */
Oid get_ts_dict_oid(List *fc_names, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_dict_name;
	Oid			fc_namespaceId;
	Oid			fc_dictoid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_dict_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_dictoid = InvalidOid;
		else
			fc_dictoid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid,
									  PointerGetDatum(fc_dict_name),
									  ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_dictoid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid,
									  PointerGetDatum(fc_dict_name),
									  ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_dictoid))
				break;
		}
	}

	if (!OidIsValid(fc_dictoid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("text search dictionary \"%s\" does not exist",
						NameListToString(fc_names))));

	return fc_dictoid;
}

/*
 * TSDictionaryIsVisible
 *		确定一个字典（由 OID 标识）在当前搜索路径中是否可见。可见的意思是“通过搜索未限定的字典名称可以找到”。
 */
bool TSDictionaryIsVisible(Oid fc_dictId)
{
	HeapTuple	fc_tup;
	Form_pg_ts_dict fc_form;
	Oid			fc_namespace;
	bool		fc_visible;

	fc_tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(fc_dictId));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search dictionary %u",
			 fc_dictId);
	fc_form = (Form_pg_ts_dict) GETSTRUCT(fc_tup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_namespace = fc_form->dictnamespace;
	if (fc_namespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_namespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名字典隐藏。
		 * 因此，我们必须对冲突字典进行缓慢检查。
		 */
		char	   *fc_name = NameStr(fc_form->dictname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			if (fc_namespaceId == fc_namespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(TSDICTNAMENSP,
									  PointerGetDatum(fc_name),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_tup);

	return fc_visible;
}

/*
 * get_ts_template_oid - 通过可能的限定名称查找 TS 模板
 *
 * 如果未找到，则返回 InvalidOid（如果 missing_ok），否则抛出错误
 */
Oid get_ts_template_oid(List *fc_names, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_template_name;
	Oid			fc_namespaceId;
	Oid			fc_tmploid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_template_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_tmploid = InvalidOid;
		else
			fc_tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP, Anum_pg_ts_template_oid,
									  PointerGetDatum(fc_template_name),
									  ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP, Anum_pg_ts_template_oid,
									  PointerGetDatum(fc_template_name),
									  ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_tmploid))
				break;
		}
	}

	if (!OidIsValid(fc_tmploid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("text search template \"%s\" does not exist",
						NameListToString(fc_names))));

	return fc_tmploid;
}

/*
 * TSTemplateIsVisible
 *		确定一个模板（由 OID 标识）在当前搜索路径中是否可见。可见的意思是“通过搜索未限定的模板名称可以找到”。
 */
bool TSTemplateIsVisible(Oid fc_tmplId)
{
	HeapTuple	fc_tup;
	Form_pg_ts_template fc_form;
	Oid			fc_namespace;
	bool		fc_visible;

	fc_tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(fc_tmplId));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search template %u", fc_tmplId);
	fc_form = (Form_pg_ts_template) GETSTRUCT(fc_tup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_namespace = fc_form->tmplnamespace;
	if (fc_namespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_namespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名模板隐藏。因此
		 * 我们必须对冲突模板进行缓慢检查。
		 */
		char	   *fc_name = NameStr(fc_form->tmplname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			if (fc_namespaceId == fc_namespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(TSTEMPLATENAMENSP,
									  PointerGetDatum(fc_name),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_tup);

	return fc_visible;
}

/*
 * get_ts_config_oid - 通过可能的限定名称查找 TS 配置
 *
 * 如果未找到，则返回 InvalidOid（如果 missing_ok），否则抛出错误
 */
Oid get_ts_config_oid(List *fc_names, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_config_name;
	Oid			fc_namespaceId;
	Oid			fc_cfgoid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, &fc_config_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_cfgoid = InvalidOid;
		else
			fc_cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP, Anum_pg_ts_config_oid,
									 PointerGetDatum(fc_config_name),
									 ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP, Anum_pg_ts_config_oid,
									 PointerGetDatum(fc_config_name),
									 ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_cfgoid))
				break;
		}
	}

	if (!OidIsValid(fc_cfgoid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("text search configuration \"%s\" does not exist",
						NameListToString(fc_names))));

	return fc_cfgoid;
}

/*
 * TSConfigIsVisible
 *		确定一个文本搜索配置（由 OID 标识）在当前搜索路径中是否可见。可见的意思是“通过搜索未限定的文本搜索配置名称可以找到”。
 */
bool TSConfigIsVisible(Oid fc_cfgid)
{
	HeapTuple	fc_tup;
	Form_pg_ts_config fc_form;
	Oid			fc_namespace;
	bool		fc_visible;

	fc_tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(fc_cfgid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search configuration %u",
			 fc_cfgid);
	fc_form = (Form_pg_ts_config) GETSTRUCT(fc_tup);

	fc_recomputeNamespacePath();

	/*
	 * 快速检查：如果它根本不在路径中，它就不可见。系统命名空间中的项肯定在路径中，因此我们甚至不需要对它们进行list_member_oid()的检查。
	 */
	fc_namespace = fc_form->cfgnamespace;
	if (fc_namespace != PG_CATALOG_NAMESPACE &&
		!list_member_oid(activeSearchPath, fc_namespace))
		fc_visible = false;
	else
	{
		/*
		 * 如果在路径中，它可能仍然不可见；它可能被路径中较早的同名配置隐藏。因此我们必须对冲突配置进行缓慢检查。
		 */
		char	   *fc_name = NameStr(fc_form->cfgname);
		ListCell   *fc_l;

		fc_visible = false;
		foreach(fc_l, activeSearchPath)
		{
			Oid			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			if (fc_namespaceId == fc_namespace)
			{
				/* 首先在路径中找到它 */
				fc_visible = true;
				break;
			}
			if (SearchSysCacheExists2(TSCONFIGNAMENSP,
									  PointerGetDatum(fc_name),
									  ObjectIdGetDatum(fc_namespaceId)))
			{
				/* 首先在路径中找到其他东西 */
				break;
			}
		}
	}

	ReleaseSysCache(fc_tup);

	return fc_visible;
}


/*
 * DeconstructQualifiedName
 *		给定作为字符串节点列表表达的可能限定名称，
 *		提取模式名称和对象名称。
 *
 * *nspname_p 如果没有显式的模式名称，则设置为 NULL。
 */
void DeconstructQualifiedName(List *fc_names,
						 char **fc_nspname_p,
						 char **fc_objname_p)
{
	char	   *fc_catalogname;
	char	   *fc_schemaname = NULL;
	char	   *fc_objname = NULL;

	switch (list_length(fc_names))
	{
		case 1:
			fc_objname = strVal(linitial(fc_names));
			break;
		case 2:
			fc_schemaname = strVal(linitial(fc_names));
			fc_objname = strVal(lsecond(fc_names));
			break;
		case 3:
			fc_catalogname = strVal(linitial(fc_names));
			fc_schemaname = strVal(lsecond(fc_names));
			fc_objname = strVal(lthird(fc_names));

			
/*
			 * 我们检查目录名称，然后忽略它。
			 */
			if (strcmp(fc_catalogname, get_database_name(MyDatabaseId)) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cross-database references are not implemented: %s",
								NameListToString(fc_names))));
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("improper qualified name (too many dotted names): %s",
							NameListToString(fc_names))));
			break;
	}

	*fc_nspname_p = fc_schemaname;
	*fc_objname_p = fc_objname;
}

/*
 * LookupNamespaceNoError
 *		查找模式名称。
 *
 * 返回命名空间OID，如果未找到则返回InvalidOid。
 *
 * 注意，这不会执行任何权限检查——调用者负责确保进行适当的检查。
 * 在大多数情况下，LookupExplicitNamespace更可取。
 */
Oid LookupNamespaceNoError(const char *fc_nspname)
{
	/* 检查 pg_temp 别名 */
	if (strcmp(fc_nspname, "pg_temp") == 0)
	{
		if (OidIsValid(myTempNamespace))
		{
			InvokeNamespaceSearchHook(myTempNamespace, true);
			return myTempNamespace;
		}

		/*
		 * 由于这仅用于查找现有对象，因此在此初始化临时命名空间没有意义；
		 * 这样做可能会给某些调用者带来问题。只需报告“未找到”。
		 */
		return InvalidOid;
	}

	return get_namespace_oid(fc_nspname, true);
}

/*
 * LookupExplicitNamespace
 *		处理明确定义的模式名称：查找模式并验证我们在其中具有USAGE（查找）权限。
 *
 * 返回命名空间OID
 */
Oid LookupExplicitNamespace(const char *fc_nspname, bool fc_missing_ok)
{
	Oid			fc_namespaceId;
	AclResult	fc_aclresult;

	/* 检查 pg_temp 别名 */
	if (strcmp(fc_nspname, "pg_temp") == 0)
	{
		if (OidIsValid(myTempNamespace))
			return myTempNamespace;

		/*
		 * 由于这仅用于查找现有对象，因此在此初始化临时命名空间没有意义；
		 * 这样做可能会给某些调用者带来问题——只需放弃。
		 */
	}

	fc_namespaceId = get_namespace_oid(fc_nspname, fc_missing_ok);
	if (fc_missing_ok && !OidIsValid(fc_namespaceId))
		return InvalidOid;

	fc_aclresult = pg_namespace_aclcheck(fc_namespaceId, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   fc_nspname);
	/* 这个查找的模式搜索钩子 */
	InvokeNamespaceSearchHook(fc_namespaceId, true);

	return fc_namespaceId;
}

/*
 * LookupCreationNamespace
 *		查找模式并验证我们在其上具有CREATE权限。
 *
 * 这与LookupExplicitNamespace类似，只是检查的权限不同，并且我们愿意根据需要创建pg_temp。
 *
 * 注意：调用此方法可能会导致CommandCounterIncrement操作，
 * 如果我们必须创建或清理临时命名空间。
 */
Oid LookupCreationNamespace(const char *fc_nspname)
{
	Oid			fc_namespaceId;
	AclResult	fc_aclresult;

	/* 检查 pg_temp 别名 */
	if (strcmp(fc_nspname, "pg_temp") == 0)
	{
		/* 初始化临时命名空间 */
		fc_AccessTempTableNamespace(false);
		return myTempNamespace;
	}

	fc_namespaceId = get_namespace_oid(fc_nspname, false);

	fc_aclresult = pg_namespace_aclcheck(fc_namespaceId, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   fc_nspname);

	return fc_namespaceId;
}

/*
 * 对切换命名空间的常见检查。
 *
 * 如果旧命名空间或新命名空间是临时模式（或临时 toast 模式），
 * 或者如果旧命名空间或新命名空间是 TOAST 模式，我们会发出警告。
 */
void CheckSetNamespace(Oid fc_oldNspOid, Oid fc_nspOid)
{
	/* 不允许重命名为临时模式或从临时模式重命名 */
	if (isAnyTempNamespace(fc_nspOid) || isAnyTempNamespace(fc_oldNspOid))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot move objects into or out of temporary schemas")));

	/* TOAST 模式也一样 */
	if (fc_nspOid == PG_TOAST_NAMESPACE || fc_oldNspOid == PG_TOAST_NAMESPACE)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot move objects into or out of TOAST schema")));
}

/*
 * QualifiedNameGetCreationNamespace
 *		给定一个可能带有限定符的对象名称（字符串列表格式），
 *		确定该对象应该创建在哪个命名空间中。
 *		还提取并返回对象名称（列表的最后一个组件）。
 *
 * 注意：这不适用任何权限检查。在适当时，调用者必须检查
 * 对所选命名空间的 CREATE 权限。
 *
 * 注意：调用此函数可能会导致 CommandCounterIncrement 操作，
 * 如果我们必须创建或清理临时命名空间。
 */
Oid QualifiedNameGetCreationNamespace(List *fc_names, char **fc_objname_p)
{
	char	   *fc_schemaname;
	Oid			fc_namespaceId;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_names, &fc_schemaname, fc_objname_p);

	if (fc_schemaname)
	{
		/* 检查 pg_temp 别名 */
		if (strcmp(fc_schemaname, "pg_temp") == 0)
		{
			/* 初始化临时命名空间 */
			fc_AccessTempTableNamespace(false);
			return myTempNamespace;
		}
		/* 使用给定的确切架构 */
		fc_namespaceId = get_namespace_oid(fc_schemaname, false);
		/* 我们在这里不检查使用权限！ */
	}
	else
	{
		/* 使用默认创建命名空间 */
		fc_recomputeNamespacePath();
		if (activeTempCreationPending)
		{
			/* 需要初始化临时命名空间 */
			fc_AccessTempTableNamespace(true);
			return myTempNamespace;
		}
		fc_namespaceId = activeCreationNamespace;
		if (!OidIsValid(fc_namespaceId))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_SCHEMA),
					 errmsg("no schema has been selected to create in")));
	}

	return fc_namespaceId;
}

/*
 * get_namespace_oid - 给定一个命名空间名称，查找其 OID
 *
 * 如果 missing_ok 为 false，当找不到命名空间名称时抛出错误。
 * 如果为 true，则返回 InvalidOid。
 */
Oid get_namespace_oid(const char *fc_nspname, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(NAMESPACENAME, Anum_pg_namespace_oid,
						  CStringGetDatum(fc_nspname));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_SCHEMA),
				 errmsg("schema \"%s\" does not exist", fc_nspname)));

	return fc_oid;
}

/*
 * makeRangeVarFromNameList
 *		工具例程，将限定名称列表转换为 RangeVar 形式。
 */
RangeVar *
makeRangeVarFromNameList(List *fc_names)
{
	RangeVar   *fc_rel = makeRangeVar(NULL, NULL, -1);

	switch (list_length(fc_names))
	{
		case 1:
			fc_rel->relname = strVal(linitial(fc_names));
			break;
		case 2:
			fc_rel->schemaname = strVal(linitial(fc_names));
			fc_rel->relname = strVal(lsecond(fc_names));
			break;
		case 3:
			fc_rel->catalogname = strVal(linitial(fc_names));
			fc_rel->schemaname = strVal(lsecond(fc_names));
			fc_rel->relname = strVal(lthird(fc_names));
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("improper relation name (too many dotted names): %s",
							NameListToString(fc_names))));
			break;
	}

	return fc_rel;
}

/*
 * NameListToString
 *		工具例程，将限定名称列表转换为字符串。
 *
 * 这主要用于生成错误消息，因此我们不对列表元素进行引号处理，
 * 以便于可读性。
 *
 * 在大多数场景中，列表元素应该始终是字符串值，
 * 但我们也允许 A_Star 以方便 ColumnRef 处理。
 */
char * NameListToString(List *fc_names)
{
	StringInfoData fc_string;
	ListCell   *fc_l;

	initStringInfo(&fc_string);

	foreach(fc_l, fc_names)
	{
		Node	   *fc_name = (Node *) lfirst(fc_l);

		if (fc_l != list_head(fc_names))
			appendStringInfoChar(&fc_string, '.');

		if (IsA(fc_name, String))
			appendStringInfoString(&fc_string, strVal(fc_name));
		else if (IsA(fc_name, A_Star))
			appendStringInfoChar(&fc_string, '*');
		else
			elog(ERROR, "unexpected node type in name list: %d",
				 (int) nodeTag(fc_name));
	}

	return fc_string.data;
}

/*
 * NameListToQuotedString
 *		工具例程，将限定名称列表转换为字符串。
 *
 * 除了在必要时对名称进行双引号处理之外，
 * 其他与上述相同，因此字符串可以被重新解析（例如，通过 textToQualifiedNameList）。
 */
char * NameListToQuotedString(List *fc_names)
{
	StringInfoData fc_string;
	ListCell   *fc_l;

	initStringInfo(&fc_string);

	foreach(fc_l, fc_names)
	{
		if (fc_l != list_head(fc_names))
			appendStringInfoChar(&fc_string, '.');
		appendStringInfoString(&fc_string, quote_identifier(strVal(lfirst(fc_l))));
	}

	return fc_string.data;
}

/*
 * isTempNamespace - 给定的命名空间是我临时表的命名空间吗？
 */
bool isTempNamespace(Oid fc_namespaceId)
{
	if (OidIsValid(myTempNamespace) && myTempNamespace == fc_namespaceId)
		return true;
	return false;
}

/*
 * isTempToastNamespace - 给定的命名空间是我的临时 toast 表
 *		命名空间吗？
 */
bool isTempToastNamespace(Oid fc_namespaceId)
{
	if (OidIsValid(myTempToastNamespace) && myTempToastNamespace == fc_namespaceId)
		return true;
	return false;
}

/*
 * isTempOrTempToastNamespace - 给定的命名空间是我的临时表
 *		命名空间还是我的临时 toast 表命名空间？
 */
bool isTempOrTempToastNamespace(Oid fc_namespaceId)
{
	if (OidIsValid(myTempNamespace) &&
		(myTempNamespace == fc_namespaceId || myTempToastNamespace == fc_namespaceId))
		return true;
	return false;
}

/*
 * isAnyTempNamespace - 给定的命名空间是临时表命名空间
 * （我的或其他后端的）吗？临时 toast 表命名空间也包括在内。
 */
bool isAnyTempNamespace(Oid fc_namespaceId)
{
	bool		fc_result;
	char	   *fc_nspname;

	/* 如果命名空间名称以 "pg_temp_" 或 "pg_toast_temp_" 开头，则为真 */
	fc_nspname = get_namespace_name(fc_namespaceId);
	if (!fc_nspname)
		return false;			/* 没有这样的命名空间？ */
	fc_result = (strncmp(fc_nspname, "pg_temp_", 8) == 0) ||
		(strncmp(fc_nspname, "pg_toast_temp_", 14) == 0);
	pfree(fc_nspname);
	return fc_result;
}

/*
 * isOtherTempNamespace - 给定的命名空间是其他后端的
 * 临时表命名空间（包括临时 toast 表命名空间）吗？
 *
 * 注意：在 C 代码的多数情况下，此函数已过时。请使用
 * RELATION_IS_OTHER_TEMP() 来检测非本地临时关系。
 */
bool isOtherTempNamespace(Oid fc_namespaceId)
{
	/* 如果是我自己的临时命名空间，返回 "false" */
	if (isTempOrTempToastNamespace(fc_namespaceId))
		return false;
	/* 否则，如果是任何临时命名空间，返回 "true" */
	return isAnyTempNamespace(fc_namespaceId);
}

/*
 * checkTempNamespaceStatus - 给定的命名空间是否由某个后端所有且正在使用？
 *
 * 注意：这可用于扫描 pg_class 中的关系，以检测
 * 孤立的临时表或有后端连接到特定数据库的命名空间。
 * 结果可能很快过时，因此调用者必须小心处理此信息。
 */
TempNamespaceStatus
checkTempNamespaceStatus(Oid fc_namespaceId)
{
	PGPROC	   *fc_proc;
	int			fc_backendId;

	Assert(OidIsValid(MyDatabaseId));

	fc_backendId = GetTempNamespaceBackendId(fc_namespaceId);

	/* 没有这样的命名空间，或者其名称表明它不是临时的？ */
	if (fc_backendId == InvalidBackendId)
		return TEMP_NAMESPACE_NOT_TEMP;

	/* 后端是否活着？ */
	fc_proc = BackendIdGetProc(fc_backendId);
	if (fc_proc == NULL)
		return TEMP_NAMESPACE_IDLE;

	/* 后端是否连接到我们正在查看的同一数据库？ */
	if (fc_proc->databaseId != MyDatabaseId)
		return TEMP_NAMESPACE_IDLE;

	/* 后端是否拥有该临时命名空间？ */
	if (fc_proc->tempNamespaceId != fc_namespaceId)
		return TEMP_NAMESPACE_IDLE;

	/* 是的，所以命名空间是忙碌的 */
	return TEMP_NAMESPACE_IN_USE;
}

/*
 * GetTempNamespaceBackendId - 如果给定的命名空间是临时表
 * 命名空间（我的或其他后端的），返回拥有它的 BackendId。
 * 临时 toast 表命名空间也包括在内。
 * 如果它不是临时命名空间，则返回 InvalidBackendId。
 */
int GetTempNamespaceBackendId(Oid fc_namespaceId)
{
	int			fc_result;
	char	   *fc_nspname;

	/* 查看命名空间名称是否以 "pg_temp_" 或 "pg_toast_temp_" 开头 */
	fc_nspname = get_namespace_name(fc_namespaceId);
	if (!fc_nspname)
		return InvalidBackendId;	/* 没有这样的命名空间？ */
	if (strncmp(fc_nspname, "pg_temp_", 8) == 0)
		fc_result = atoi(fc_nspname + 8);
	else if (strncmp(fc_nspname, "pg_toast_temp_", 14) == 0)
		fc_result = atoi(fc_nspname + 14);
	else
		fc_result = InvalidBackendId;
	pfree(fc_nspname);
	return fc_result;
}

/*
 * GetTempToastNamespace - 获取我的临时 toast 表命名空间的 OID，
 * 它必须已经分配。（此仅在为临时表创建 toast 表时使用，
 * 因此我们必须已经完成 InitTempTableNamespace）
 */
Oid GetTempToastNamespace(void)
{
	Assert(OidIsValid(myTempToastNamespace));
	return myTempToastNamespace;
}


/*
 * GetTempNamespaceState - 获取会话的临时命名空间状态
 *
 * 这用于传递状态给并行工作者，不适用于通用访问。
 */
void GetTempNamespaceState(Oid *fc_tempNamespaceId, Oid *fc_tempToastNamespaceId)
{
	/* 返回命名空间 OID，如果会话尚未创建临时命名空间，则为 0 */
	*fc_tempNamespaceId = myTempNamespace;
	*fc_tempToastNamespaceId = myTempToastNamespace;
}

/*
 * SetTempNamespaceState - 设置会话的临时命名空间状态
 *
 * 这用于传递状态给并行工作者，不适用于通用访问。
 * 通过将这些命名空间 OID 转移到工作者，
 * 我们确保它们将拥有与其领导者相同的搜索路径概念。
 */
void SetTempNamespaceState(Oid fc_tempNamespaceId, Oid fc_tempToastNamespaceId)
{
	/* 工作者不应创建自己的命名空间... */
	Assert(myTempNamespace == InvalidOid);
	Assert(myTempToastNamespace == InvalidOid);
	Assert(myTempNamespaceSubID == InvalidSubTransactionId);

	/* 分配与领导者具有相同的命名空间 OIDs */
	myTempNamespace = fc_tempNamespaceId;
	myTempToastNamespace = fc_tempToastNamespaceId;

	/*
	 * 让我 myTempNamespaceSubID == InvalidSubTransactionId 没关系。
	 * 即使命名空间在领导者看来是新的，但对于工作者来说并不是新的，
	 * 我们肯定不希望工作者尝试销毁它。
	 */

	baseSearchPathValid = false;	/* 可能需要重建列表 */
}


/*
 * GetOverrideSearchPath - 获取当前搜索路径定义，采用 PushOverrideSearchPath 使用的形式。
 *
 * 结果结构在指定的内存上下文中分配
 * （可能等于 CurrentMemoryContext，也可能不等）；但任何
 * 由重新验证计算创建的垃圾将位于 CurrentMemoryContext 中。
 */
OverrideSearchPath *
GetOverrideSearchPath(MemoryContext fc_context)
{
	OverrideSearchPath *fc_result;
	List	   *fc_schemas;
	MemoryContext fc_oldcxt;

	fc_recomputeNamespacePath();

	fc_oldcxt = MemoryContextSwitchTo(fc_context);

	fc_result = (OverrideSearchPath *) palloc0(sizeof(OverrideSearchPath));
	fc_schemas = list_copy(activeSearchPath);
	while (fc_schemas && linitial_oid(fc_schemas) != activeCreationNamespace)
	{
		if (linitial_oid(fc_schemas) == myTempNamespace)
			fc_result->addTemp = true;
		else
		{
			Assert(linitial_oid(fc_schemas) == PG_CATALOG_NAMESPACE);
			fc_result->addCatalog = true;
		}
		fc_schemas = list_delete_first(fc_schemas);
	}
	fc_result->schemas = fc_schemas;
	fc_result->generation = activePathGeneration;

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_result;
}

/*
 * CopyOverrideSearchPath - 复制指定的 OverrideSearchPath。
 *
 * 结果结构在 CurrentMemoryContext 中分配。
 */
OverrideSearchPath *
CopyOverrideSearchPath(OverrideSearchPath *fc_path)
{
	OverrideSearchPath *fc_result;

	fc_result = (OverrideSearchPath *) palloc(sizeof(OverrideSearchPath));
	fc_result->schemas = list_copy(fc_path->schemas);
	fc_result->addCatalog = fc_path->addCatalog;
	fc_result->addTemp = fc_path->addTemp;
	fc_result->generation = fc_path->generation;

	return fc_result;
}

/*
 * OverrideSearchPathMatchesCurrent - 路径是否与当前设置匹配？
 *
 * 在某些常见代码路径中反复测试，并且在活动搜索路径较少更改的典型场景中，它将始终成功。
 * 我们通过保持一个生成计数器来加快这一情况，该计数器在活动搜索路径更改时增加。
 */
bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *fc_path)
{
	ListCell   *fc_lc,
			   *fc_lcp;

	fc_recomputeNamespacePath();

	/* 如果已经知道等于活动路径，则快速退出。 */
	if (fc_path->generation == activePathGeneration)
		return true;

	/* 我们扫描 activeSearchPath 以查看它是否与输入匹配。 */
	fc_lc = list_head(activeSearchPath);

	/* 如果 path->addTemp，第一个项目应该是我的临时命名空间。 */
	if (fc_path->addTemp)
	{
		if (fc_lc && lfirst_oid(fc_lc) == myTempNamespace)
			fc_lc = lnext(activeSearchPath, fc_lc);
		else
			return false;
	}
	/* 如果 path->addCatalog，下一个项目应该是 pg_catalog。 */
	if (fc_path->addCatalog)
	{
		if (fc_lc && lfirst_oid(fc_lc) == PG_CATALOG_NAMESPACE)
			fc_lc = lnext(activeSearchPath, fc_lc);
		else
			return false;
	}
	/* 我们现在应该查看 activeCreationNamespace。 */
	if (activeCreationNamespace != (fc_lc ? lfirst_oid(fc_lc) : InvalidOid))
		return false;
	/* activeSearchPath 的其余部分应该与 path->schemas 匹配。 */
	foreach(fc_lcp, fc_path->schemas)
	{
		if (fc_lc && lfirst_oid(fc_lc) == lfirst_oid(fc_lcp))
			fc_lc = lnext(activeSearchPath, fc_lc);
		else
			return false;
	}
	if (fc_lc)
		return false;

	/*
	 * 更新 path->generation，以便未来的测试可以快速返回，
	 * 只要活动搜索路径不变。
	 */
	fc_path->generation = activePathGeneration;

	return true;
}

/*
 * PushOverrideSearchPath - 暂时覆盖搜索路径
 *
 * 不要使用此函数；几乎任何使用方式都会引入安全
 * 漏洞。此函数存在是为了支持在
 * 非安全敏感环境中运行的旧代码。
 *
 * 我们允许嵌套覆盖，因此使用了 push/pop 术语。
 * 当覆盖处于活动状态时，将忽略 GUC
 * search_path 变量。
 *
 * 有可能 newpath->useTemp 被设置但不再有任何活动临时命名空间，如果路径是在创建临时命名空间的事务期间保存的，然后被回滚。在这种情况下，我们只会忽略 useTemp。一个合理的替代方案是创建一个新的临时命名空间，但对于现有调用者来说没有必要，因为一个空的临时命名空间无论如何不会影响他们的结果。
 *
 * 还值得注意的是，newpath 中列出的其他模式可能也不再存在。我们对此不担心，因为与不存在的命名空间匹配的 OID 在搜索中将不会产生任何命中。
 */
void PushOverrideSearchPath(OverrideSearchPath *fc_newpath)
{
	OverrideStackEntry *fc_entry;
	List	   *fc_oidlist;
	Oid			fc_firstNS;
	MemoryContext fc_oldcxt;

	/*
	 * 复制列表以便安全保存，并根据需要插入隐式搜索
	 * 命名空间。此代码应跟踪 recomputeNamespacePath。
	 */
	fc_oldcxt = MemoryContextSwitchTo(TopMemoryContext);

	fc_oidlist = list_copy(fc_newpath->schemas);

	/*
	 * 记住显式列表的第一个成员。
	 */
	if (fc_oidlist == NIL)
		fc_firstNS = InvalidOid;
	else
		fc_firstNS = linitial_oid(fc_oidlist);

	/*
	 * 将任何隐式搜索的命名空间添加到列表中。注意这些是在
	 * 前面，而不是后面；还要注意我们不检查这些的 USAGE
	 * 权限。
	 */
	if (fc_newpath->addCatalog)
		fc_oidlist = lcons_oid(PG_CATALOG_NAMESPACE, fc_oidlist);

	if (fc_newpath->addTemp && OidIsValid(myTempNamespace))
		fc_oidlist = lcons_oid(myTempNamespace, fc_oidlist);

	/*
	 * 构建新的堆栈条目，然后将其插入列表的头部。
	 */
	fc_entry = (OverrideStackEntry *) palloc(sizeof(OverrideStackEntry));
	fc_entry->searchPath = fc_oidlist;
	fc_entry->creationNamespace = fc_firstNS;
	fc_entry->nestLevel = GetCurrentTransactionNestLevel();

	overrideStack = lcons(fc_entry, overrideStack);

	/* 并使其处于活动状态。 */
	activeSearchPath = fc_entry->searchPath;
	activeCreationNamespace = fc_entry->creationNamespace;
	activeTempCreationPending = false;	/* XXX 这样可以吗？ */

	/*
	 * 我们在推送/弹出覆盖路径时始终增加 activePathGeneration。
	 * 在当前使用中，这些操作总是会改变
	 * 有效路径状态，因此没有必要检查它是否
	 * 没有改变。
	 */
	activePathGeneration++;

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * PopOverrideSearchPath - 撤销先前的 PushOverrideSearchPath
 *
 * 在（子）事务中的任何推送将在中止时自动弹出。
 * 但是如果在正常控制流中没有弹出推送，这就是调用者的错误。
 */
void PopOverrideSearchPath(void)
{
	OverrideStackEntry *fc_entry;

	/* 安全检查。 */
	if (overrideStack == NIL)
		elog(ERROR, "bogus PopOverrideSearchPath call");
	fc_entry = (OverrideStackEntry *) linitial(overrideStack);
	if (fc_entry->nestLevel != GetCurrentTransactionNestLevel())
		elog(ERROR, "bogus PopOverrideSearchPath call");

	/* 弹出堆栈并释放存储。 */
	overrideStack = list_delete_first(overrideStack);
	list_free(fc_entry->searchPath);
	pfree(fc_entry);

	/* 激活下一个级别。 */
	if (overrideStack)
	{
		fc_entry = (OverrideStackEntry *) linitial(overrideStack);
		activeSearchPath = fc_entry->searchPath;
		activeCreationNamespace = fc_entry->creationNamespace;
		activeTempCreationPending = false;	/* XXX 这样可以吗？ */
	}
	else
	{
		/* 如果 baseSearchPathValid 无效，这没有用但无害 */
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;
	}

	/* 如上所述，代数始终增加。 */
	activePathGeneration++;
}


/*
 * get_collation_oid - 通过可能的合格名称查找排序规则
 *
 * 注意，这只会查找与当前
 * 数据库的编码兼容的排序规则。
 */
Oid get_collation_oid(List *fc_name, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_collation_name;
	int32		fc_dbencoding = GetDatabaseEncoding();
	Oid			fc_namespaceId;
	Oid			fc_colloid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_name, &fc_schemaname, &fc_collation_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			return InvalidOid;

		fc_colloid = fc_lookup_collation(fc_collation_name, fc_namespaceId, fc_dbencoding);
		if (OidIsValid(fc_colloid))
			return fc_colloid;
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_colloid = fc_lookup_collation(fc_collation_name, fc_namespaceId, fc_dbencoding);
			if (OidIsValid(fc_colloid))
				return fc_colloid;
		}
	}

	/* 在路径中未找到 */
	if (!fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("collation \"%s\" for encoding \"%s\" does not exist",
						NameListToString(fc_name), GetDatabaseEncodingName())));
	return InvalidOid;
}

/*
 * get_conversion_oid - 通过可能的限定名称查找转换
 */
Oid get_conversion_oid(List *fc_name, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_conversion_name;
	Oid			fc_namespaceId;
	Oid			fc_conoid = InvalidOid;
	ListCell   *fc_l;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_name, &fc_schemaname, &fc_conversion_name);

	if (fc_schemaname)
	{
		/* 使用给定的确切架构 */
		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (fc_missing_ok && !OidIsValid(fc_namespaceId))
			fc_conoid = InvalidOid;
		else
			fc_conoid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid,
									 PointerGetDatum(fc_conversion_name),
									 ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 在搜索路径中搜索它 */
		fc_recomputeNamespacePath();

		foreach(fc_l, activeSearchPath)
		{
			fc_namespaceId = lfirst_oid(fc_l);

			if (fc_namespaceId == myTempNamespace)
				continue;		/* 不要在临时命名空间中查找 */

			fc_conoid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid,
									 PointerGetDatum(fc_conversion_name),
									 ObjectIdGetDatum(fc_namespaceId));
			if (OidIsValid(fc_conoid))
				return fc_conoid;
		}
	}

	/* 在路径中未找到 */
	if (!OidIsValid(fc_conoid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("conversion \"%s\" does not exist",
						NameListToString(fc_name))));
	return fc_conoid;
}

/*
 * FindDefaultConversionProc - 查找默认编码转换过程
 */
Oid FindDefaultConversionProc(int32 fc_for_encoding, int32 fc_to_encoding)
{
	Oid			fc_proc;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不要在临时命名空间中查找 */

		fc_proc = FindDefaultConversion(fc_namespaceId, fc_for_encoding, fc_to_encoding);
		if (OidIsValid(fc_proc))
			return fc_proc;
	}

	/* 在路径中未找到 */
	return InvalidOid;
}

/*
 * recomputeNamespacePath - 如果需要，重新计算派生变量的路径。
 */
static void fc_recomputeNamespacePath(void)
{
	Oid			fc_roleid = GetUserId();
	char	   *fc_rawname;
	List	   *fc_namelist;
	List	   *fc_oidlist;
	List	   *fc_newpath;
	ListCell   *fc_l;
	bool		fc_temp_missing;
	Oid			fc_firstNS;
	bool		fc_pathChanged;
	MemoryContext fc_oldcxt;

	/* 如果激活了重写搜索规范，则不执行任何操作。 */
	if (overrideStack)
		return;

	/* 如果路径已经有效，则不执行任何操作。 */
	if (baseSearchPathValid && namespaceUser == fc_roleid)
		return;

	/* 需要一个可修改的namespace_search_path字符串副本 */
	fc_rawname = pstrdup(namespace_search_path);

	/* 将字符串解析为标识符列表 */
	if (!SplitIdentifierString(fc_rawname, ',', &fc_namelist))
	{
		/* 名称列表中的语法错误 */
		/* 如果GUC检查了check_search_path，这种情况不应该发生 */
		elog(ERROR, "invalid list syntax");
	}

	/*
	 * 将名称列表转换为OID列表。如果任何名称不可识别或我们没有读取权限，就将其排除在列表之外。
	 * （我们不能引发错误，因为search_path设置已经被接受。）也不要重复条目。
	 */
	fc_oidlist = NIL;
	fc_temp_missing = false;
	foreach(fc_l, fc_namelist)
	{
		char	   *fc_curname = (char *) lfirst(fc_l);
		Oid			fc_namespaceId;

		if (strcmp(fc_curname, "$user") == 0)
		{
			/* $user --- 如果有，替换与用户名匹配的namespace */
			HeapTuple	fc_tuple;

			fc_tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
			if (HeapTupleIsValid(fc_tuple))
			{
				char	   *fc_rname;

				fc_rname = NameStr(((Form_pg_authid) GETSTRUCT(fc_tuple))->rolname);
				fc_namespaceId = get_namespace_oid(fc_rname, true);
				ReleaseSysCache(fc_tuple);
				if (OidIsValid(fc_namespaceId) &&
					!list_member_oid(fc_oidlist, fc_namespaceId) &&
					pg_namespace_aclcheck(fc_namespaceId, fc_roleid,
										  ACL_USAGE) == ACLCHECK_OK &&
					InvokeNamespaceSearchHook(fc_namespaceId, false))
					fc_oidlist = lappend_oid(fc_oidlist, fc_namespaceId);
			}
		}
		else if (strcmp(fc_curname, "pg_temp") == 0)
		{
			/* pg_temp --- 如果有，替换临时namespace */
			if (OidIsValid(myTempNamespace))
			{
				if (!list_member_oid(fc_oidlist, myTempNamespace) &&
					InvokeNamespaceSearchHook(myTempNamespace, false))
					fc_oidlist = lappend_oid(fc_oidlist, myTempNamespace);
			}
			else
			{
				/* 如果应为创建namespace，则设置标志 */
				if (fc_oidlist == NIL)
					fc_temp_missing = true;
			}
		}
		else
		{
			/* 正常的namespace引用 */
			fc_namespaceId = get_namespace_oid(fc_curname, true);
			if (OidIsValid(fc_namespaceId) &&
				!list_member_oid(fc_oidlist, fc_namespaceId) &&
				pg_namespace_aclcheck(fc_namespaceId, fc_roleid,
									  ACL_USAGE) == ACLCHECK_OK &&
				InvokeNamespaceSearchHook(fc_namespaceId, false))
				fc_oidlist = lappend_oid(fc_oidlist, fc_namespaceId);
		}
	}

	/*
	 * 记住显式列表的第一个成员。（注意：如果temp_missing，这在名义上是错误的，但我们仍然需要它来区分
	 * pg_catalog的显式和隐式提及。）
	 */
	if (fc_oidlist == NIL)
		fc_firstNS = InvalidOid;
	else
		fc_firstNS = linitial_oid(fc_oidlist);

	/*
	 * 将任何隐式搜索的命名空间添加到列表中。注意这些是在
	 * 前面，而不是后面；还要注意我们不检查这些的 USAGE
	 * 权限。
	 */
	if (!list_member_oid(fc_oidlist, PG_CATALOG_NAMESPACE))
		fc_oidlist = lcons_oid(PG_CATALOG_NAMESPACE, fc_oidlist);

	if (OidIsValid(myTempNamespace) &&
		!list_member_oid(fc_oidlist, myTempNamespace))
		fc_oidlist = lcons_oid(myTempNamespace, fc_oidlist);

	/*
	 * 我们想要检测核心search path变量的有效值没有改变的情况。只要我们在这样做，就可以避免不必要地复制OID列表。
	 */
	if (baseCreationNamespace == fc_firstNS &&
		baseTempCreationPending == fc_temp_missing &&
		equal(fc_oidlist, baseSearchPath))
	{
		fc_pathChanged = false;
	}
	else
	{
		fc_pathChanged = true;

		/* 必须在永久存储中保存OID列表。 */
		fc_oldcxt = MemoryContextSwitchTo(TopMemoryContext);
		fc_newpath = list_copy(fc_oidlist);
		MemoryContextSwitchTo(fc_oldcxt);

		/* 现在可以安全地赋值给状态变量。 */
		list_free(baseSearchPath);
		baseSearchPath = fc_newpath;
		baseCreationNamespace = fc_firstNS;
		baseTempCreationPending = fc_temp_missing;
	}

	/* 标记路径有效。 */
	baseSearchPathValid = true;
	namespaceUser = fc_roleid;

	/* 并使其处于活动状态。 */
	activeSearchPath = baseSearchPath;
	activeCreationNamespace = baseCreationNamespace;
	activeTempCreationPending = baseTempCreationPending;

	/*
	 * 仅在实际更改时才提升生成。如果注意到比较的是基础路径变量的旧状态；所以
	 * 这不能处理我们刚刚弹出重写路径并恢复基础路径的情况。相反，我们依赖重写弹出逻辑来提升生成。
	 */
	if (fc_pathChanged)
		activePathGeneration++;

	/* 清理。 */
	pfree(fc_rawname);
	list_free(fc_namelist);
	list_free(fc_oidlist);
}

/*
 * AccessTempTableNamespace
 *		提供对临时namespace的访问，必要时可能创建它
 *		如果尚未存在。这条例程注册了该namespace在该事务中的使用。
 *		'force'可以设置为true，以允许调用者强制创建用于
 *		此后端的临时namespace，这发生在其创建处于待处理状态时。
 */
static void fc_AccessTempTableNamespace(bool fc_force)
{
	/*
	 * 注意在此事务中已访问此临时namespace。
	 */
	MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;

	/*
	 * 如果调用者尝试访问临时模式，期望namespace的创建处于待处理状态并应被强制，则执行创建。
	 */
	if (!fc_force && OidIsValid(myTempNamespace))
		return;

	/*
	 * 临时表空间尚不存在且需要，因此初始化它。
	 */
	fc_InitTempTableNamespace();
}

/*
 * InitTempTableNamespace
 *		在特定后端的首次使用时初始化临时表namespace
 */
static void fc_InitTempTableNamespace(void)
{
	char		fc_namespaceName[NAMEDATALEN];
	Oid			fc_namespaceId;
	Oid			fc_toastspaceId;

	Assert(!OidIsValid(myTempNamespace));

	/*
	 * 首先，进行权限检查以查看我们是否有权限创建临时表。
	 * 我们在这里使用非标准的错误消息，因为“数据库名称：
	 * 权限被拒绝”可能有点晦涩。
	 *
	 * 注意，ACL_CREATE_TEMP权限在pg_namespace_aclmask中重新检查；
	 * 这是必要的，因为当前用户ID可能在会话中更改。
	 * 但是，在未由具有适当权限的人发出临时表创建请求之前，无需首先创建namespace。
	 */
	if (pg_database_aclcheck(MyDatabaseId, GetUserId(),
							 ACL_CREATE_TEMP) != ACLCHECK_OK)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to create temporary tables in database \"%s\"",
						get_database_name(MyDatabaseId))));

	/*
	 * 不允许热备份会话创建临时表。除了修改系统目录的问题外，还有一个命名冲突：
	 * pg_temp_N属于主服务器上具有BackendId N的会话，而不是具有相同BackendId的热备份会话。
	 * 由于XactReadOnly检查，我们不应该能够到达这里，但让我们确保一下。注意，这也支持允许
	 * XactReadOnly事务修改临时表的各种操作；如果没有这个，它们需要RecoveryInProgress检查。
	 */
	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
				 errmsg("cannot create temporary tables during recovery")));

	/* 并行工作线程也不能创建临时表。 */
	if (IsParallelWorker())
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
				 errmsg("cannot create temporary tables during a parallel operation")));

	snprintf(fc_namespaceName, sizeof(fc_namespaceName), "pg_temp_%d", MyBackendId);

	fc_namespaceId = get_namespace_oid(fc_namespaceName, true);
	if (!OidIsValid(fc_namespaceId))
	{
		/*
		 * 在此数据库中首次使用此临时namespace；创建它。临时namespace总是由超级用户拥有。
		 * 我们将其权限保持在默认状态 --- 即，除了超级用户外没有访问权限 --- 以确保无权用户无法查看其他后端的
		 * 临时表。这样做是因为访问我自己后端的临时namespace的地方跳过了对其的权限检查。
		 */
		fc_namespaceId = NamespaceCreate(fc_namespaceName, BOOTSTRAP_SUPERUSERID,
									  true);
		/* 提升命令计数器以使namespace可见 */
		CommandCounterIncrement();
	}
	else
	{
		/*
		 * 如果namespace已经存在，清空它（以防以前的所有者崩溃而未执行此操作）。
		 */
		fc_RemoveTempRelations(fc_namespaceId);
	}

	/*
	 * 如果对应的toast-table namespace尚不存在，则创建它。（我们假设如果它存在则无需清空，因为
	 * 删除父表应该使其toast表消失。）
	 */
	snprintf(fc_namespaceName, sizeof(fc_namespaceName), "pg_toast_temp_%d",
			 MyBackendId);

	fc_toastspaceId = get_namespace_oid(fc_namespaceName, true);
	if (!OidIsValid(fc_toastspaceId))
	{
		fc_toastspaceId = NamespaceCreate(fc_namespaceName, BOOTSTRAP_SUPERUSERID,
									   true);
		/* 提升命令计数器以使namespace可见 */
		CommandCounterIncrement();
	}

	
	myTempNamespace = fc_namespaceId;
	myTempToastNamespace = fc_toastspaceId;

	/*
	 * 标记 MyProc 拥有此命名空间，其他进程可以使用它来决定
	 * 暂时命名空间是否正在使用。我们假设 namespaceId 的分配是一个原子操作。
	 * 即使不是，导致创建这个临时命名空间的临时关系仍然在当前事务提交之前是锁定的，
	 * 并且其 pg_namespace 行尚不可见。 但这不重要：
	 * 这个标志表明命名空间正在使用，因此在其上创建的对象不会被并发删除。
	 */
	MyProc->tempNamespaceId = fc_namespaceId;

	/* 应该还没有完成。 */
	AssertState(myTempNamespaceSubID == InvalidSubTransactionId);
	myTempNamespaceSubID = GetCurrentSubTransactionId();

	baseSearchPathValid = false;	/* 需要重建列表 */
}

/*
 * 事务结束时的命名空间清理。
 */
void AtEOXact_Namespace(bool fc_isCommit, bool fc_parallel)
{
	/*
	 * 如果我们中止了选择临时命名空间的事务，
	 * 那么我们必须重新执行任何创建或清理工作。 因此，在下一次之前，
	 * 完全忘记这个命名空间。 另一方面，如果我们提交，则注册一个退出回调
	 * 在后端关闭时清理临时表。 （我们只想在每个会话中注册一次回调，
	 * 所以这是一个很好的地方来做。）
	 */
	if (myTempNamespaceSubID != InvalidSubTransactionId && !fc_parallel)
	{
		if (fc_isCommit)
			before_shmem_exit(fc_RemoveTempRelationsCallback, 0);
		else
		{
			myTempNamespace = InvalidOid;
			myTempToastNamespace = InvalidOid;
			baseSearchPathValid = false;	/* 需要重建列表 */

			/*
			 * 重置 MyProc 中的临时命名空间标志。 我们假设这个操作是原子性的。
			 *
			 * 因为这个事务正在中止，pg_namespace 行对其他任何人来说都不可见，
			 * 但这无关紧要：如果该命名空间中包含的对象被并发删除，
			 * 这不是问题。
			 */
			MyProc->tempNamespaceId = InvalidOid;
		}
		myTempNamespaceSubID = InvalidSubTransactionId;
	}

	/*
	 * 如果有人未能执行 PopOverrideSearchPath，则进行清理
	 */
	if (overrideStack)
	{
		if (fc_isCommit)
			elog(WARNING, "leaked override search path");
		while (overrideStack)
		{
			OverrideStackEntry *fc_entry;

			fc_entry = (OverrideStackEntry *) linitial(overrideStack);
			overrideStack = list_delete_first(overrideStack);
			list_free(fc_entry->searchPath);
			pfree(fc_entry);
		}
		/* 如果 baseSearchPathValid 无效，这没有用但无害 */
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;
		/* 始终增加生成 --- 请参阅重新计算命名空间路径中的说明 */
		activePathGeneration++;
	}
}

/*
 * AtEOSubXact_Namespace
 *
 * 在子事务提交时，将临时命名空间创建标志传播到父子事务。
 *
 * 在子事务中止时，如果我们设置了标志，则忘记该标志。
 */
void AtEOSubXact_Namespace(bool fc_isCommit, SubTransactionId fc_mySubid,
					  SubTransactionId fc_parentSubid)
{
	OverrideStackEntry *fc_entry;

	if (myTempNamespaceSubID == fc_mySubid)
	{
		if (fc_isCommit)
			myTempNamespaceSubID = fc_parentSubid;
		else
		{
			myTempNamespaceSubID = InvalidSubTransactionId;
			/* TEMP 命名空间创建失败，因此重置状态 */
			myTempNamespace = InvalidOid;
			myTempToastNamespace = InvalidOid;
			baseSearchPathValid = false;	/* 需要重建列表 */

			/*
			 * 重置 MyProc 中的临时命名空间标志。 我们假设这个操作是原子性的。
			 *
			 * 因为这个子事务正在中止，pg_namespace 行对其他任何人来说都不可见，
			 * 但这无关紧要：如果该命名空间中包含的对象被并发删除，
			 * 这不是问题。
			 */
			MyProc->tempNamespaceId = InvalidOid;
		}
	}

	/*
	 * 如果有人未能执行 PopOverrideSearchPath，则进行清理
	 */
	while (overrideStack)
	{
		fc_entry = (OverrideStackEntry *) linitial(overrideStack);
		if (fc_entry->nestLevel < GetCurrentTransactionNestLevel())
			break;
		if (fc_isCommit)
			elog(WARNING, "leaked override search path");
		overrideStack = list_delete_first(overrideStack);
		list_free(fc_entry->searchPath);
		pfree(fc_entry);
		/* 始终增加生成 --- 请参阅重新计算命名空间路径中的说明 */
		activePathGeneration++;
	}

	/* 激活下一个级别。 */
	if (overrideStack)
	{
		fc_entry = (OverrideStackEntry *) linitial(overrideStack);
		activeSearchPath = fc_entry->searchPath;
		activeCreationNamespace = fc_entry->creationNamespace;
		activeTempCreationPending = false;	/* XXX 这样可以吗？ */

		/*
		 * 在这里增加生成可能是不必要的，但这不应该是一个性能关键的情况，
		 * 所以最好还是过于谨慎。
		 */
		activePathGeneration++;
	}
	else
	{
		/* 如果 baseSearchPathValid 无效，这没有用但无害 */
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;

		/*
		 * 如果我们弹出了一个覆盖栈条目，那么我们已经增加了上面的生成。
		 * 如果没有，则上述赋值没有任何作用，我们无需增加生成。
		 */
	}
}

/*
 * 删除指定临时命名空间中的所有关系。
 *
 * 这在后端关闭时调用（如果我们创建了任何临时关系）。
 * 当我们开始使用预先存在的临时命名空间时，也会调用它，
 * 以清除可能已由崩溃的后端创建的任何关系。
 */
static void fc_RemoveTempRelations(Oid fc_tempNamespaceId)
{
	ObjectAddress fc_object;

	/*
	 * 我们想要清除目标命名空间中的所有内容，但不删除命名空间本身
	 * （仅仅删除它然后再重新创建将浪费周期）。因此，指定 SKIP_ORIGINAL。
	 * 这也是一个内部删除，我们希望不删除可能拥有临时对象的任何扩展。
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_tempNamespaceId;
	fc_object.objectSubId = 0;

	performDeletion(&fc_object, DROP_CASCADE,
					PERFORM_DELETION_INTERNAL |
					PERFORM_DELETION_QUIETLY |
					PERFORM_DELETION_SKIP_ORIGINAL |
					PERFORM_DELETION_SKIP_EXTENSIONS);
}

/*
 * 后端退出时清除临时关系的回调。
 */
static void fc_RemoveTempRelationsCallback(int fc_code, Datum fc_arg)
{
	if (OidIsValid(myTempNamespace))	/* 应始终为真 */
	{
		/* 需要确保我们有一个可用的事务。 */
		AbortOutOfAnyTransaction();
		StartTransactionCommand();
		PushActiveSnapshot(GetTransactionSnapshot());

		fc_RemoveTempRelations(myTempNamespace);

		PopActiveSnapshot();
		CommitTransactionCommand();
	}
}

/*
 * 从临时命名空间中删除所有临时表。
 */
void ResetTempTableNamespace(void)
{
	if (OidIsValid(myTempNamespace))
		fc_RemoveTempRelations(myTempNamespace);
}


/*
 * 处理 GUC 变量 'search_path' 的例程。
 */

/* check_hook: 验证新的 search_path 值 */
bool check_search_path(char **fc_newval, void **fc_extra, GucSource fc_source)
{
	char	   *fc_rawname;
	List	   *fc_namelist;

	/* 需要可修改的字符串副本 */
	fc_rawname = pstrdup(*fc_newval);

	/* 将字符串解析为标识符列表 */
	if (!SplitIdentifierString(fc_rawname, ',', &fc_namelist))
	{
		/* 名称列表中的语法错误 */
		GUC_check_errdetail("List syntax is invalid.");
		pfree(fc_rawname);
		list_free(fc_namelist);
		return false;
	}

	/*
	 * 我们过去曾尝试检查指定的模式是否存在，但对于包含不存在的模式的
	 * search_path 设置有许多有效的用例；而且通常我们在这里不在事务中，
	 * 所以无论如何无法检查系统目录。 因此，现在唯一的要求是标识符列表的语法有效性。
	 */

	pfree(fc_rawname);
	list_free(fc_namelist);

	return true;
}

/* assign_hook：根据需要执行额外操作 */
void assign_search_path(const char *fc_newval, void *fc_extra)
{
	/*
	 * 我们将路径标记为需要重新计算，但在需要之前不做任何事情。
	 * 这避免了在 GUC 初始化期间或在事务之外进行数据库访问。
	 */
	baseSearchPathValid = false;
}

/*
 * InitializeSearchPath: 在 InitPostgres 时初始化模块。
 *
 * 在我们足够准备好进行目录查找之后调用此函数。
 */
void InitializeSearchPath(void)
{
	if (IsBootstrapProcessingMode())
	{
		/*
		 * 在引导模式下，搜索路径必须为 'pg_catalog' 以便在正确的命名空间中创建表；
		 * 忽略 GUC 设置。
		 */
		MemoryContext fc_oldcxt;

		fc_oldcxt = MemoryContextSwitchTo(TopMemoryContext);
		baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE);
		MemoryContextSwitchTo(fc_oldcxt);
		baseCreationNamespace = PG_CATALOG_NAMESPACE;
		baseTempCreationPending = false;
		baseSearchPathValid = true;
		namespaceUser = GetUserId();
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;
		activePathGeneration++; /* 正式 */
	}
	else
	{
		/*
		 * 在正常模式下，为 pg_namespace 或 pg_authid 行的任何 syscache
		 * 无效的回调进行安排。 （更改角色名称可能会影响特殊字符串 $user 的含义。）
		 */
		CacheRegisterSyscacheCallback(NAMESPACEOID,
									  fc_NamespaceCallback,
									  (Datum) 0);
		CacheRegisterSyscacheCallback(AUTHOID,
									  fc_NamespaceCallback,
									  (Datum) 0);
		/* 在下次使用时强制重新计算搜索路径 */
		baseSearchPathValid = false;
	}
}

/*
 * NamespaceCallback
 *		系统缓存无效回调函数
 */
static void fc_NamespaceCallback(Datum fc_arg, int fc_cacheid, uint32 fc_hashvalue)
{
	/* 在下次使用时强制重新计算搜索路径 */
	baseSearchPathValid = false;
}


List * fetch_search_path(bool fc_includeImplicit)
{
	List	   *fc_result;

	fc_recomputeNamespacePath();

	/*
	 * 如果临时命名空间应该在第一位，强制其存在。这是为了
	 * 让调用者可以相信结果反映了实际的默认创建命名空间。
	 * 在这里这样做有点牵强，因为current_schema() supposedly是一个
	 * 没有副作用的稳定函数，但其他选项似乎更糟糕。
	 */
	if (activeTempCreationPending)
	{
		fc_AccessTempTableNamespace(true);
		fc_recomputeNamespacePath();
	}

	fc_result = list_copy(activeSearchPath);
	if (!fc_includeImplicit)
	{
		while (fc_result && linitial_oid(fc_result) != activeCreationNamespace)
			fc_result = list_delete_first(fc_result);
	}

	return fc_result;
}

/*
 * 将活动搜索路径获取到调用者分配的OID数组中。
 * 返回路径条目的数量。（如果这超过了sarray_len，
 * 那么数据没有全部存储。）
 *
 * 返回的列表总是包含隐式预置的命名空间，
 * 但从不包括临时命名空间。（这适合现有用户，
 * 他们无论如何都会想要忽略临时命名空间。）这个
 * 定义使我们不必担心初始化临时命名空间。
 */
int fetch_search_path_array(Oid *fc_sarray, int fc_sarray_len)
{
	int			fc_count = 0;
	ListCell   *fc_l;

	fc_recomputeNamespacePath();

	foreach(fc_l, activeSearchPath)
	{
		Oid			fc_namespaceId = lfirst_oid(fc_l);

		if (fc_namespaceId == myTempNamespace)
			continue;			/* 不包括临时命名空间 */

		if (fc_count < fc_sarray_len)
			fc_sarray[fc_count] = fc_namespaceId;
		fc_count++;
	}

	return fc_count;
}


/*
 * 导出FooIsVisible函数作为可SQL调用的函数。
 *
 * 注意：自Postgres 8.4起，如果在
 * 一个不存在的对象OID上调用这些函数，它们将静默返回NULL，
 * 而不是失败。这是为了避免竞争
 * 条件错误，当一个使用MVCC
 * 快照扫描目录的查询使用这些函数时。
 * 基础的IsVisible函数总是使用最新的快照，
 * 因此可能会在对象仍对事务快照可见时将其视为已经消失。
 * （当前编码中没有竞争
 * 条件，因为我们不接受sinval消息
 * 在SearchSysCacheExists测试和随后的查找之间。）
 */

Datum pg_table_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(RelationIsVisible(fc_oid));
}

Datum pg_type_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(TypeIsVisible(fc_oid));
}

Datum pg_function_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(FunctionIsVisible(fc_oid));
}

Datum pg_operator_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(OPEROID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(OperatorIsVisible(fc_oid));
}

Datum pg_opclass_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(CLAOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(OpclassIsVisible(fc_oid));
}

Datum pg_opfamily_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(OPFAMILYOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(OpfamilyIsVisible(fc_oid));
}

Datum pg_collation_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(COLLOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(CollationIsVisible(fc_oid));
}

Datum pg_conversion_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(CONVOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(ConversionIsVisible(fc_oid));
}

Datum pg_statistics_obj_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(STATEXTOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(StatisticsObjIsVisible(fc_oid));
}

Datum pg_ts_parser_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(TSPARSEROID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(TSParserIsVisible(fc_oid));
}

Datum pg_ts_dict_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(TSDICTOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(TSDictionaryIsVisible(fc_oid));
}

Datum pg_ts_template_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(TSTEMPLATEOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(TSTemplateIsVisible(fc_oid));
}

Datum pg_ts_config_is_visible(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	if (!SearchSysCacheExists1(TSCONFIGOID, ObjectIdGetDatum(fc_oid)))
		PG_RETURN_NULL();

	PG_RETURN_BOOL(TSConfigIsVisible(fc_oid));
}

Datum pg_my_temp_schema(PG_FUNCTION_ARGS)
{
	PG_RETURN_OID(myTempNamespace);
}

Datum pg_is_other_temp_schema(PG_FUNCTION_ARGS)
{
	Oid			fc_oid = PG_GETARG_OID(0);

	PG_RETURN_BOOL(isOtherTempNamespace(fc_oid));
}
